reactjs - React useState 重新渲染触发器的行为取决于其调用时间
问题描述
假设你有一个有多个 useState 的钩子,例如:
function useMyHook() {
const [s1, setS1] = useState();
const [s2, setS2] = useState();
const setFn = useCallback(
() => {
console.log('set s1');
setS1(Date.now());
console.log('set s2');
setS2(Date.now());
},
[setS1, setS2]
);
return setFn;
}
现在,根据您调用的时间,setFn
反应将触发一个或两个重新渲染。
- 一次重新渲染:
function MyComponent() {
console.log('render')
const setTwoStates = useMyHook()
// sync call => triggers only one re render
useEffect(() => setTwoStates(), [])
// console.logs are: render, set s1, set s2, render
return (
<div>hello</div>
);
}
- 两个重新渲染:
function MyComponent() {
console.log('render')
const setTwoStates = useMyHook()
// as soon as the setFn is called in a later tick, react triggers two re renders
useEffect(() => setTimeout(setTwoStates, 1), [])
// console.logs are: render, set s1, render, set s2, render
return (
<div>hello</div>
);
}
有人对此有解释吗?这可能会导致意外行为,具体取决于您调用钩子的方式。
我还创建了一个小的示例存储库,以防你想玩这个
解决方案
当 React 在 useEffect 回调或事件处理程序(函数正在运行时)中设置状态时,React 将批处理状态设置器,但在您的异步示例中,状态是在效果函数返回后设置的。
const syncFn = () =>
console.log('in sync function');
const asyncFn = () => setTimeout(
()=>console.log('in async function'),
10
);
syncFn();
console.log('sync function returned');
asyncFn();
console.log('async function returned');
您可以在该片段中看到in async function
日志, async function returned
因此任何设置状态都将在效果回调或事件处理程序返回后发生,并且除非您明确告诉 React 对其进行批处理,否则无法对其进行批处理。
您可以使用unstable_batchedUpdates
来告诉 react 批量更新:
const App = () => {
const [item1, setItem1] = React.useState(0);
const [item2, setItem2] = React.useState(0);
const render = React.useRef(0);
//mutate render ref (shows how often component is rendered)
render.current++;
const asyncUpdates = React.useCallback(() => {
Promise.resolve().then(() => {
ReactDOM.unstable_batchedUpdates(() => {
setItem1((item) => item + 1);
setItem2((item) => item + 1);
});
});
}, []);
const syncUpdates = React.useCallback(() => {
setItem1((item) => item + 1);
setItem2((item) => item + 1);
}, []);
const asyncNonBatchedUpdates = React.useCallback(() => {
Promise.resolve().then(() => {
setItem1((item) => item + 1);
setItem2((item) => item + 1);
});
}, []);
//using asyncUpdates in effect on mount
React.useEffect(asyncUpdates, []);
return (
<div>
<h1>Rendered: {render.current}</h1>
<div>
<div>item1:{item1}</div>
<div>item2:{item2}</div>
</div>
{/* run async updates as event handler */}
<button onClick={asyncUpdates}>async updates</button>
<button onClick={syncUpdates}>sync updates</button>
<button onClick={asyncNonBatchedUpdates}>
async non bathed updates
</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
推荐阅读
- c - C二叉搜索树后序遍历使用堆栈迭代
- python - 如何为 flask_cors 设置 Access-Control-Max-Age?
- node.js - 如何处理被阻止的用户验证电子邮件?
- python - pipenv 安装错误:错误的解释器:没有这样的文件或目录
- android - 我想要从身份验证方法中获取的详细信息转换为实时数据库
- javascript - Wget 403 禁止,但适用于所有浏览器
- css - TYPO3 8.7.22 页面 UID CSS
- bash - 自动化 docker 以运行测试脚本
- android - Android Studio 使用 Flutter 获取模拟器名称
- html - Safari 不显示显示:网格