在业务中有个需求,所有的组件都是动态渲染和展示,父组件会展示不确定个数的子组件,子组件中会有显隐判断,如果不满足条件则return null;如果所有子组件都返回null不再展示UI,但是父组件却还有一些公用的样式或者业务逻辑还在运行,那怎么让父组件在子组件都return null的时候也不再展示呢
import "./App.css";
import ParentTest from "./components/testNullChild/parent";
import ChildTest from "./components/testNullChild/child";
function App() {
return (
<>
<ParentTest>
{[1, 2, 3, 4].map((item: number) => {
return <ChildTest key={item} sort={item}></ChildTest>;
})}
</ParentTest>
</>
);
}
export default App;
// 父组件
import React, {
type FC,
} from "react";
const ParentTest: FC<any> = (props) => {
return <div>{props.children}</div>
};
export default ParentTest;
// 子组件
const ChildTest = (props: any) => {
const visabled = props.sort % 2 === 0 ? true : false;
if (visabled) {
return <div>Hello World</div>;
}
return null;
};
export default ChildTest;
第一种方法
// 子组件
尝试给porps.__proto__.$visabled添加自定义属性,然后在父组件中遍历children,判断如果props.__proto__.$visabled 都为false则判定为所有子组件都为null
const ChildTest = (props: any) => {
const visabled = props.sort % 2 === 0 ? 1 : 0;
props.__proto__$visbled = visabled;
if (visabled) {
return <div>Hello World</div>;
}
return null;
};
export default ChildTest;
// 父组件
const ParentTest: FC<any> = (props) => {
const [nullLength, setNullLength] = useState(0);
useEffect(() => {
React.Children.map(props.children, (child) => {
console.log(child);
const vis = child.props.$visbled;
if (vis != undefined) {
if (vis === 0) {
setNullLength((pre: number) => pre + 1);
}
}
});
console.log(props.children);
}, [props.children]);
return (
<>
{nullLength === props.children.length ? null : (
<div style={{ backgroundColor: "#ef2345" }}>{props.children}</div>
)}
</>
);
};
export default ParentTest;
虽然可以加上属性并且可以到达效果但是浏览器会有莫名的render报错,所以就filter掉了
第二种方法
调用child.type这个方法,判断其返回的值,如果是null则表示子组件渲染为空,如果所有的子组件都为null则隐藏父组件
// 父组件
const ParentTest: FC<any> = (props) => {
const [nullLength, setNullLength] = useState(0);
useEffect(() => {
React.Children.map(props.children, (child) => {
// 子组件返回的是不是null 就是调用子组件的type方法
// 注意一定要传入原有的props作为入参, 不然子组件会找不到props
const typeContext = child.type(child.props);
console.log("typeContext", typeContext);
if (typeContext === null) {
setNullLength((pre: number) => pre + 1);
}
});
console.log(props.children);
}, [props.children]);
return (
<>
{nullLength === props.children.length ? null : (
<div style={{ backgroundColor: "#ef2345", height: 300 }}>
{props.children}
</div>
)}
</>
);
};
export default ParentTest;
同样可以到达效果,但是仔细查看下图:
会发现每个子组件都渲染了两遍,其实调用child.type这个方法就是重新走了一遍render渲染的逻辑,这样的话同样的组件都渲染两次,影响性能不说可能还会造成其他不可描述的错误,所以这个也filter了
第三种方法(终极方案)
咱们知道子组件要修改父组件的数据是不能直接修改,需要通过回调函数来调用后间接修改父组件的数据,那么在不确定子组件数量并且是动态的子组件时,父组件怎么动态的给子组件添加属性呢?话不多说直接上代码:
// 父组件
const ParentTest: FC<any> = (props) => {
const [nullLength, setNullLength] = useState(0);
const newChildren = useMemo(() => {
return React.Children.map(props.children, (child) => {
return cloneElement(child, {
setNullLength: () => setNullLength((pre: number) => pre + 1),
});
});
}, [props.children]);
useEffect(() => {
console.log(nullLength);
}, [nullLength]);
return (
<>
{nullLength === props.children.length ? null : (
<div style={{ backgroundColor: "#ddd", height: 300, width: 300 }}>
{newChildren}
</div>
)}
</>
);
};
export default ParentTest;
//子组件
const ChildTest = (props: any) => {
console.log("props.sort", props);
// const visabled = props.sort % 2 === 0 ? 1 : 0;
const visabled = 0;
useEffect(() => {
if (visabled === 0) {
if (props.setNullLength) {
props.setNullLength();
}
}
}, [visabled]);
if (visabled) {
return <div style={{ color: "red" }}>Hello World</div>;
}
return null;
};
export default ChildTest;
看图得知,子组件都是只调用一次,避免了多次渲染的问题