根据父状态反应功能组件更新子状态

时间:2020-09-25 18:42:01

标签: reactjs parent

比方说,我们有一个内部状态为isOpen的手风琴组件,因此您可以关闭并打开该组件。

我们现在想拥有一个父组件,该组件的状态也为isOpen并具有按钮。在此组件中,我们有2次Accordion,并且将Accordion isOpen传递给我们,我们希望如果父项的状态更改为isOpen Accordion,则接受此。

所有组件都是功能组件

 const Accordion = ({ isOpen: parentIsOpen = false }) => {
    const [isOpen, setIsOpen] = useState(parentIsOpen);
    const handleSetIsOpen = () => setIsOpen(!isOpen);
    return (
      <div>
        I'm open: {isOpen}
        <button onClick={handleSetIsOpen}>toggle isOpen child</button>
      </div>
    );
  };

  const MasterComponent = () => {
    const [isOpen, setIsOpen] = useState(false);
    const handleSetIsOpen = () => setIsOpen(!isOpen);
    return (
      <div>
        <button onClick={handleSetIsOpen}>toggle isOpen parent</button>
        <Accordion isOpen={isOpen} />
        <Accordion isOpen={isOpen} />
      </div>
    );
  };

在这种情况下,以上的手风琴将首先呈现为父状态isOpen的初始状态。如果我们按下按钮切换为isOpen parent,则将更改父状态,但不会更新子状态。

要解决此问题,我们可以使用useEffect

  const Accordion = ({ isOpen: parentIsOpen = false }) => {
    const [isOpen, setIsOpen] = useState(parentIsOpen);
    const handleSetIsOpen = () => setIsOpen(!isOpen);

    useEffect(() => {
      if (parentIsOpen !== isOpen) {
        setIsOpen(parentIsOpen);
      }
    }, [parentIsOpen]);

    return (
      <div>
        I'm open: {isOpen}
        <button onClick={handleSetIsOpen}>toggle isOpen child</button>
      </div>
    );
  };

  const MasterComponent = () => {
    const [isOpen, setIsOpen] = useState(false);
    const handleSetIsOpen = () => setIsOpen(!isOpen);
    return (
      <div>
        <button onClick={handleSetIsOpen}>toggle isOpen parent</button>
        <Accordion isOpen={isOpen} />
        <Accordion isOpen={isOpen} />
      </div>
    );
  };

在这种情况下,当父级更改isOpen状态时,将正确更新子级。

有一个问题:

“ React Hook useEffect缺少依赖项:'isOpen'。要么包含它,要么删除依赖项数组react-hooks / exhaustive-deps”

因此,如何消除esLint抱怨的问题,并且我们不想在其中放置isOpen,因为它会导致错误。

如果我们将isOpen添加到数组中,就像这样:

 useEffect(() => {
      if (parentIsOpen !== isOpen) {
        setIsOpen(parentIsOpen);
      }
    }, [parentIsOpen, isOpen]);

然后将出现一种情况,我们将按手风琴单击内部按钮并更新内部状态,然后useEffect将运行,并看到父级与子级具有不同的状态,并立即设置旧状态。

所以基本上,您有一个循环,手风琴将永远不会打开。

问题是基于父状态更新子状态的最佳方法是什么?

请不要建议将所有状态置于父状态,并传递没有孩子状态的道具。这将不起作用,因为此示例中的两个手风琴都必须具有自己的状态,并且能够以独立的方式打开和关闭,但是,如果父母说“关闭”或“打开”,则应听取它的声音。

谢谢!

2 个答案:

答案 0 :(得分:1)

您要说的不建议实际上是我将提供的解决方案……您将需要状态来控制父组件的isOpen。另外,您应该在父级中有单独的方法来控制每个手风琴的状态,并传递给props中的每个手风琴……

不确定为什么要为子组件分配单独的状态。我相信这样就可以了。

const MasterComponent = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [isOpen1, setIsOpen1] = useState(false);
  const [isOpen2, setIsOpen2] = useState(false);

  const handleParentClose = () => {
    setIsOpen(false);
    setIsOpen1(false);
    setIsOpen2(false);
  }

  return (
    <div>
      <button onClick={handleParentClose}>toggle isOpen parent</button>
      <Accordion isOpen={isOpen1} setIsOpen={setIsOpen1} />
      <Accordion isOpen={isOpen2} setIsOpen={setIsOpen2} />
    </div>
 );
};

const Accordion = props => {
 return (
   <div>
    I'm open: {props.isOpen}
    <button onClick={props.setIsOpen}>toggle isOpen child</button>
   </div>
   );
  }

这不包括用于实际可见性切换的代码,但状态在那里。

编辑:添加了在父关闭时关闭手风琴的代码。

答案 1 :(得分:0)

实际上我会说这是做到这一点的方式

 const Accordion = ({ isOpen: parentIsOpen = false }) => {
    const [isOpen, setIsOpen] = useState(parentIsOpen);
    const handleSetIsOpen = () => setIsOpen(!isOpen);

    useEffect(() => {
        setIsOpen(parentIsOpen);
    }, [parentIsOpen]);

    return (
      <div>
        I'm open: {isOpen}
        <button onClick={handleSetIsOpen}>toggle isOpen child</button>
      </div>
    );
  };

  const MasterComponent = () => {
    const [isOpen, setIsOpen] = useState(false);
    const handleSetIsOpen = () => setIsOpen(!isOpen);
    return (
      <div>
        <button onClick={handleSetIsOpen}>toggle isOpen parent</button>
        <Accordion isOpen={isOpen} />
        <Accordion isOpen={isOpen} />
      </div>
    );
  };

因此,只需删除子组件中的状态检查,让他更新状态即可,但是由于使用相同的值进行更新,因此不会重新渲染或发生某些昂贵的行为。

今天对其进行了测试,并进行了检查(如果状态不同或不相同),则如果更新的状态与以前相同,则应注意不要触发重新渲染。