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

Posted

技术标签:

【中文标题】根据父状态更新子状态 react 功能组件【英文标题】:Update child state based on parent state react functional components 【发布时间】:2021-01-12 02:55:38 【问题描述】:

假设我们有一个组件 Accordion,其内部状态为 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>
    );
  ;

在这种情况下,Accordion 将作为初始状态父 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 将运行并看到父级与子级具有不同的状态并立即设置旧状态。

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

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

请不要建议将 all-state 放在 parent 并传递没有 child state 的 props。这是行不通的,因为本例中的两个手风琴都必须有自己的状态,并且能够以独立的方式打开和关闭,但是如果父级说关闭或打开,它应该听。

谢谢!

【问题讨论】:

【参考方案1】:

其实我会说这是这样做的方法

 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>
    );
  ;

所以只需删除子组件中的状态检查,让他更新状态,但由于更新为相同的值,它不会重新渲染或执行一些昂贵的行为。

今天对其进行了测试,并进行了检查,如果状态不同或没有相同,如果更新的状态与以前相同,react 会注意不触发重新渲染。

【讨论】:

【参考方案2】:

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

不确定为什么要为子组件设置单独的状态。我相信这样的事情就足够了。

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>
   );
  

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

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

【讨论】:

以上是关于根据父状态更新子状态 react 功能组件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用从 React 中的子组件获取的数据更新父组件中的状态

React Native - 处理来自子组件(自定义组件)的父状态,而不在父组件中添加功能

React - 父类组件功能改变子组件的类

React 功能组件在重新挂载后停止渲染状态更改

React js从父事件中更新子状态

React 子组件在父组件中更新状态后如何将其重置回原始状态