React Hook - 我总是从 useState 获取过时的值,因为子组件从不更新
React Hook - I always get stale values from useState just because child component never updates
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
更改时重新渲染,它就可以工作。这是为什么?这符合预期吗?
这确实是预期的行为。
您在这里遇到的是函数闭包。当您将 handleExpand
传递给 Child 时,所有引用的变量都是 'saved' 及其当前值。 open = []
。由于您的组件不会重新呈现,因此它不会收到 handleExpand
回调的 'new version'。每次调用都会有相同的结果。
有几种方法可以绕过这个。首先显然是让您的子组件重新渲染。
但是,如果您绝对不想重新渲染,则可以使用 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]);
}
}
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
更改时重新渲染,它就可以工作。这是为什么?这符合预期吗?
这确实是预期的行为。
您在这里遇到的是函数闭包。当您将 handleExpand
传递给 Child 时,所有引用的变量都是 'saved' 及其当前值。 open = []
。由于您的组件不会重新呈现,因此它不会收到 handleExpand
回调的 'new version'。每次调用都会有相同的结果。
有几种方法可以绕过这个。首先显然是让您的子组件重新渲染。
但是,如果您绝对不想重新渲染,则可以使用 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]);
}
}