react的子组件怎样控制父组件是否显隐

170 阅读2分钟

在业务中有个需求,所有的组件都是动态渲染和展示,父组件会展示不确定个数的子组件,子组件中会有显隐判断,如果不满足条件则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掉了

image.png

第二种方法

调用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;

同样可以到达效果,但是仔细查看下图:

image.png 会发现每个子组件都渲染了两遍,其实调用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;

image.png

看图得知,子组件都是只调用一次,避免了多次渲染的问题