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
组件重新渲染,则它可以工作。这是为什么?这是预期的吗?
答案 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]);
}
}