React Hook-我总是从useState获得陈旧的值,只是因为子组件永远不会更新

时间:2019-07-29 19:27:23

标签: javascript reactjs state react-hooks

TL; DR,这是我的父级组件:

const Parent = () => {

    const [open, setOpen] = useState([]);

    const handleExpand = panelIndex => {

        if (open.includes(panelIndex)) {
            // remove panelIndex from [...open]
            // asign new array to variable: newOpen
            // set the state

            setOpen(newOpen);

        } else {
            setOpen([...open, panelIndex]);
        }
    }

    return (
      <div>
         <Child expand={handleExpand} /> // No need to update
         <Other isExpanded={open} /> // needs to update if open changed
      </div>
    )
}

这是我的Child组件:

const Child = (props) => (
   <button
      type="button"
      onClick={() => props.expand(1)}
   >
      EXPAND PANEL 1
   </button>
);

export default React.memo(Child, () => true); // true means don't re-render

这些代码仅是示例。要点是,我不需要更新或重新渲染Child组件,因为它只是一个按钮。但是第二次单击按钮并没有触发Parent重新渲染。

如果我像这样将console.log(open)放在handleExpand内:

const handleExpand = panelIndex => {
    console.log(open);
    if (open.includes(panelIndex)) {
        // remove panelIndex from [...open]
        // asign new array to variable: newOpen
        // set the state

        setOpen(newOpen);

    } else {
        setOpen([...open, panelIndex]);
    }
}

每当我单击按钮时,它就会打印出相同的数组,好像从未更新过的open的值一样。

但是,如果我更改了<Child />后让open组件重新渲染,则它可以工作。这是为什么?这是预期的吗?

1 个答案:

答案 0 :(得分:2)

这确实是预期的行为。

您在这里遇到的是函数闭包。当您将handleExpand传递给Child时,所有引用的变量及其当前值都被“保存”。 open = []。由于您的组件不会重新渲染,因此不会收到handleExpand回调的“新版本”。每次通话都会得到相同的结果。

有几种绕过此方法的方法。首先显然是让您的Child组件重新呈现。

但是,如果您完全不想重新渲染,则可以使用useRef创建对象并访问其当前属性:

const openRef = useRef([])
const [open, setOpen] = useState(openRef.current);

// We keep our ref value synced with our state value
useEffect(() => {
  openRef.current = open;
}, [open])

const handleExpand = panelIndex => {    
    if (openRef.current.includes(panelIndex)) {
        setOpen(newOpen);    
    } else {
        // Notice we use the callback version to get the current state
        // and not a referenced state from the closure
        setOpen(open => [...open, panelIndex]);
    }
}