[原文地址](Automatic batching for fewer renders in React 18 · Discussion #21 · reactwg/react-18 · GitHub)
写在前面
最近在学习React18中相关更新,也可以说是心血来潮想翻译一些东西,也是为了自己学习,也想跟社区同学一起学习,初次翻译,有很多语义可能不同,同学们如果遇到不对的地方,可以查阅原文,也期望同学能指出文中不合事实的地方。
概述
react 18
添加开箱即用的性能改进,通过默认情况下进行更多的批处理,删除在应用程序或库代码中手动的批量更新。 这篇文章将解释批处理是什么,它以前的工作原理,以及改变了什么。
什么是批处理
批处理是指为了更好的性能将一组状态更新,融合进一次单独的重渲染中
。
例如,你如果有两个状态需要在一次点击事件中更新,React总会把它们放入一次重渲染中。如果你点击以下代码,你会发现每次你点击的时候,React只会重渲染一次,尽管你更新了两次状态。
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // 暂时不回重渲染
setFlag(f => !f); // 暂时不回重渲染
// React 只会最后重渲染一次
// React will only re-render once at the end (that's batching!)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
Demo: React 17 batches inside event handlers(注意console 中的信息)
这对性能是非常好的提升,因为它避免了很多不必要的重渲染。他还能阻止你的组件因为单个状态的更新而显示出一个半成品
的状态,有些甚至会造成一些问题。这可能会提醒你,直到你点完单后,服务员才会去厨房下单,并不会在你点第一道菜时就去。
但是在批处理中,react的表现并不一致。例如,如果你获取线上数据,然后在回调中更新状态,react并不会对这些更新进行批处理,而是进行独立的更新。
这是因为react过去只在浏览器时间中进行批处理(比如click),但是我们却在处理事件的回调中更新状态
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 17 and earlier does NOT batch these because
// they run *after* the event in a callback, not *during* it
setCount(c => c + 1); // Causes a re-render
setFlag(f => !f); // Causes a re-render
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
Demo: React 17 does NOT batch outside event handlers. (注意在一次点击中两次更新)
直到React18
,我们只在React事件处理中进行批处理。我们不会在promise
setTimeout
native Event Handle
中进行批处理。
什么是自动批处理
让我们关注到react18
,所有的更新都会被自动批处理,不管它来自哪里。
这意味着,promise
native Event Handle
或者任何其他的事件,都会像React
事件样被批处理。我们预计这会导致更少的重渲染,并在你的应用中有更好的性能。
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 18 and later DOES batch these:
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
-
✅ Demo: React 18 with
createRoot
batches even outside event handlers! (Notice one render per click in the console!) -
? Demo: React 18 with legacy
render
keeps the old behavior (Notice two renders per click in the console.)
React 会自动批处理,不管更新来自哪里,如下例
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
如下例子表现相同
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
})
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
如果我不想批处理怎么办
通常,批处理是安全的,但是有些代码可能依赖状态更新后立马从DOM获取某些东西。对于以下情况,可以用ReactDOM.flushSync()
,去跳出批处理
import { flushSync } from 'react-dom'; // Note: react-dom, not react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
我们不希望这成为常用方法
这是否会影响hook的功能
如果你正在使用hook 我们期望批处理能正常工作(如果不能请告诉我们)
这是否会影响class 的功能
记住,在React时间处理中的更新总是会被批处理,所以对于这些更新是没有影响的。
这里有一些边界case 能用来讨论一下
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
// { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
在React18 中这不会是问题了,因为即使是在setTimeout 中的更新也会被批处理,React不会同步的渲染第一个setState,这个渲染会出现在浏览器的下一帧中,所以这个渲染还没有更新
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
// { count: 0, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
案例.
如果这会成为你更新React18的阻碍,你可以使用ReactDOM.flushSync
去强制更新,但我们建议你谨慎操作
handleClick = () => {
setTimeout(() => {
ReactDOM.flushSync(() => {
this.setState(({ count }) => ({ count: count + 1 }));
});
// { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
案例.
这个问题不回影响具有hook 的函数组件,因为更新状态不会更新useState中现存的变量(猜测是指useState闭包相关的问题)
function handleClick() {
setTimeout(() => {
console.log(count); // 0
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
console.log(count); // 0
}, 1000)
虽然当你使用hook时,这种行为会惊讶到你,但他为自动批处理铺平了道路。
关于unstable_batchedUpdates
import { unstable_batchedUpdates } from 'react-dom';
unstable_batchedUpdates(() => {
setCount(c => c + 1);
setFlag(f => !f);
});
这个API在React18中仍然存在,但是不再需要他了,因为有自动批处理,我们暂时不回在18版本中移除,但是可能在主流React库不再依赖它之后移除
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!