带回调的 useState 不更新 useCallback 依赖项
useState with callback not updating useCallback dependencies
我在这里做了一个工作示例:
https://codesandbox.io/s/magical-flower-o0gyn?file=/src/App.js
当我点击隐藏按钮时,我想将更新的数据保存到本地存储:
- 我在第一列上单击隐藏:
setValueWithCallback
运行,将回调设置为 ref 并设置状态
useEffect
启动,使用更新后的数据调用回调
saveToLocalStorage
在 useCallback
中被调用,data
被设置为依赖项
问题出在第 3 步,两者保存到本地存储的内容都是 {visible: true}
。
我知道如果我改变这一行:
const saveToLocalStorage = useCallback(() => {
localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
}, [data]);
为此:
const saveToLocalStorage = localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
它有效,但我无法理解,为什么它不能与第一个一起使用。我认为它一定是某种封闭的东西,但我没有看到它。
如果data
已经更新,useEffect
运行回调,为什么不在依赖数组中更新?。
是的,这个例子很奇怪,第二个解决方案非常好,我只是想证明这个问题。
感谢您的帮助!
您的 data
是数组类型,并且仅在发生状态更新时做出 shallow comparison
反应,因此基本上它会检查 data
引用更改而不是其值更改。这就是为什么当 data's
值更改时您的 saveToLocalStorage 没有重新启动。
您的代码中的问题是因为 saveToLocalStorage
函数关闭了组件的本地状态。
在 setValueWithCallback
函数中,您使用传递给 setValueWithCallback
函数的 callback
参数保存对 saveToLocalStorage
函数的引用,这就是您的问题所在是。
useCallback
挂钩将更新 saveToLocalStorage
函数的函数引用,但您 不要 调用更新后的函数。相反,您调用保存在 callbackRef.current
中的函数,它不是更新的函数,而是一个旧函数,它在两个对象中的值都为 visible
属性 的状态上有一个闭包设置为true
.
解决方案
您可以通过将 data
作为参数传递给回调函数来解决此问题。您在调用 callbackRef.current(data)
时已经传递了参数,但您没有在 saveToLocalStorage
函数中使用它。
更改 saveToLocalStorage
以使用从 useEffect
挂钩内部传递的参数。
const saveToLocalStorage = useCallback((updatedData) => {
localStorage.setItem(
`_colProps`,
JSON.stringify(updatedData.map((e) => ({ id: e.id, visible: e.visible })))
);
}, []);
解决这个问题的另一种方法是摆脱 callbackRef
并从 useEffect
钩子内部调用 saveToLocalStorage
函数。
useEffect(() => {
saveToLocalStorage();
}, [data, saveToLocalStorage]);
我在这里做了一个工作示例: https://codesandbox.io/s/magical-flower-o0gyn?file=/src/App.js
当我点击隐藏按钮时,我想将更新的数据保存到本地存储:
- 我在第一列上单击隐藏:
setValueWithCallback
运行,将回调设置为 ref 并设置状态 useEffect
启动,使用更新后的数据调用回调saveToLocalStorage
在useCallback
中被调用,data
被设置为依赖项
问题出在第 3 步,两者保存到本地存储的内容都是 {visible: true}
。
我知道如果我改变这一行:
const saveToLocalStorage = useCallback(() => {
localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
}, [data]);
为此:
const saveToLocalStorage = localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
它有效,但我无法理解,为什么它不能与第一个一起使用。我认为它一定是某种封闭的东西,但我没有看到它。
如果data
已经更新,useEffect
运行回调,为什么不在依赖数组中更新?。
是的,这个例子很奇怪,第二个解决方案非常好,我只是想证明这个问题。
感谢您的帮助!
您的 data
是数组类型,并且仅在发生状态更新时做出 shallow comparison
反应,因此基本上它会检查 data
引用更改而不是其值更改。这就是为什么当 data's
值更改时您的 saveToLocalStorage 没有重新启动。
您的代码中的问题是因为 saveToLocalStorage
函数关闭了组件的本地状态。
在 setValueWithCallback
函数中,您使用传递给 setValueWithCallback
函数的 callback
参数保存对 saveToLocalStorage
函数的引用,这就是您的问题所在是。
useCallback
挂钩将更新 saveToLocalStorage
函数的函数引用,但您 不要 调用更新后的函数。相反,您调用保存在 callbackRef.current
中的函数,它不是更新的函数,而是一个旧函数,它在两个对象中的值都为 visible
属性 的状态上有一个闭包设置为true
.
解决方案
您可以通过将 data
作为参数传递给回调函数来解决此问题。您在调用 callbackRef.current(data)
时已经传递了参数,但您没有在 saveToLocalStorage
函数中使用它。
更改 saveToLocalStorage
以使用从 useEffect
挂钩内部传递的参数。
const saveToLocalStorage = useCallback((updatedData) => {
localStorage.setItem(
`_colProps`,
JSON.stringify(updatedData.map((e) => ({ id: e.id, visible: e.visible })))
);
}, []);
解决这个问题的另一种方法是摆脱 callbackRef
并从 useEffect
钩子内部调用 saveToLocalStorage
函数。
useEffect(() => {
saveToLocalStorage();
}, [data, saveToLocalStorage]);