【问题标题】:Why does this React setter work if it should be a stale closure?如果这个 React setter 应该是一个陈旧的闭包,为什么它会起作用?
【发布时间】:2023-01-26 13:24:13
【问题描述】:
我有这个下面的功能。我的随机化函数在渲染中是相同的,因为我将它包装在 useCallback 中。当我单击随机化按钮时,它会重新呈现我的应用程序。
然而,当我点击那个按钮时,因为randomize被记忆了,我不使用旧的setNum功能吗?这是如何运作的? setter 函数不是链接到它们各自的状态,所以陈旧的 setter 函数会改变一个过时的状态吗?将 setter 包含在依赖项中是最佳做法吗?由于代码似乎按原样工作,它有什么实际区别?
export default function App() {
const [num, setNum] = useState(0);
const randomize = useCallback(() => {
setNum(Math.random());
}, []);
return (
<div className="App">
<h4>{num}</h4>
<button onClick={randomize}>Randomize</button>
</div>
);
}
【问题讨论】:
标签:
javascript
reactjs
usecallback
【解决方案1】:
useCallback 中没有引用有状态值,因此不存在可能导致问题的陈旧状态。
此外,状态设置器是稳定的引用——它在所有渲染中都是完全相同的功能。每个不同的setNum不绑定只要到它自己的渲染 - 你可以随时调用对它的任何引用,然后组件将重新渲染。
let lastFn;
const App = () => {
const [value, setValue] = React.useState(0);
if (lastFn) {
console.log('Re-render. Setter is equal to previous setter:', lastFn === setValue);
}
lastFn = setValue;
setTimeout(() => {
setValue(value + 1);
}, 1000);
return (
<div>
{value}
</div>
);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
将 setter 包含在依赖项中是最佳做法吗?
一般来说,是的,将内部引用的所有内容作为依赖项包含在内是个好主意 - 但 ESLint 的 rules of hooks 足够智能,可以识别 useState 返回的函数是稳定的,因此不需要包含在内在依赖数组中。 (几乎所有来自 props 或 state 的东西应该虽然被包含在依赖数组中,exhaustive-deps 会在缺少某些东西时警告你)
【解决方案2】:
setter 函数不是链接到它们各自的状态,所以陈旧的 setter 函数会改变过时的状态吗?
不。
从文档:钩子 API 参考 > 基本钩子 > useState:
笔记
React 保证 setState 函数标识是稳定的并且不会在重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 依赖项列表中省略的原因。
将 setter 包含在依赖项中是最佳做法吗?
从技术上讲,这是一种反优化潜移默化运行时成本。没关系。如果它让您有信心遵守规则,请添加它。