React要更新,就像渣男会变心
今天和同事聊天,我说他是个铁憨憨,不会和女生聊天。
他啪的一下跳起来,“我可懂情调了”
“哦?那你来句土味情话。”
他清清嗓子,压低了腔调,望向远方,缓缓道:
如果我是
component
,我对你的情愫在didMount
时燃起,直到我生命unmount
时熄灭
正当他沉浸在YY的世界无法自拔时,我说:
你知道在
React18
,componentDidMount
和componentWillUnmount
可能调用多次么?
呵,渣男!
从Strict Mode谈起
React
有个特性 —— Strict Mode
,被StrictMode
包裹的组件在DEV
环境会对不推荐写法有更严格的提示与辅助检测行为。
<StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</StrictMode>
「辅助检测行为」是指部分方法会被React
重复调用,帮助开发者更容易发现不规范使用这些方法时的潜在bug
。
所有会被重复调用的API见StrictMode文档[1]
举个例子:
function App() {
const [num, update] = useState(0);
function onClick() {
update(num + 1);
}
console.log('render');
return (
<p onClick={onClick}>{num}</p>
);
}
当App
被StrictMode
包裹,点击p
触发更新后,App
组件会render
两次。
在
v17
之前,例子中console.log
会执行两次。但在v17
之后,React
覆写了console
方法,所以console.log
只会执行一次,但组件实际会render
两次
这么做的目的是:作为函数组件,App
的「副作用」应该在useEffect
回调中执行。
如果不规范书写副作用(比如在组件函数体内写副作用),那么重复render
更容易暴露可能产生的bug
。
铺垫完背景。接下来,让我们揭露React
善变的渣男行径。
最近刷v18
讨论组时突然发现:StrictMode
中会增加一条Strict Effect
规则。
Strict Effect
简单的说,类似上文讲到的部分API
在StrictMode
下会重复执行。
Strict Effect
规则会让useEffect
、useLayoutEffect
在StrictMode
下也会重复执行。
比如:
function App() {
// 或useLayoutEffect
useEffect(() => {
// 逻辑1
return () => // 逻辑2;
}, [])
// ...
}
在当前React
中,组件mount
时,执行逻辑1。
而在Strict Effect
规则下,mount
时的逻辑如下:
组件
mount
时,执行逻辑1React
模拟组件unmount
,执行逻辑2React
模拟组件mount
,执行逻辑1
注意,这里useEffect
的依赖项是[]
,在以往的认知里,依赖项为「空数组」意味着该useEffect
逻辑只会在mount
时执行一次。
而在v18
的Strict Mode
,由于包含了Strict Effect
规则,mount
时的useEffect
逻辑会被重复执行。
某种程度上讲,这种打破开发者既有认知的Breaking Change
,比Concurrent Mode
更让人难以接受。
那么React
团队为什么要设计这条规则呢?
一切为了Offscreen
Offscreen
是一个开发中的API
,预计会在某个v18
的小版本发布。
他的功能类似Vue
中的keep-alive
,用来在组件「失活」时在后台保存组件状态。
举个Tab
切换的例子,在Posts
和Archive
之间切换Tab
:
当切换到Posts
时,Archive
属于「失活」状态。
如果不需要保存状态,则销毁Archive
组件。当切换到Archive Tab
时,再重新mount Archive
。
当需要保存状态时,只能将Posts
与Archive
的状态保存在他们的父组件或状态管理(比如Redux
)中。
而有了Offscreen API
,在Fiber
树(可以理解为虚拟DOM
树)层面,可以保存失活的组件结构与状态。
这个API
的应用场景主要包括:
切换路由时保存之前路由的状态
预加载将要切换的路由
现在问题来了:当Offscreen
组件从「失活」变为「活动」,会触发什么生命周期函数呢?
答案是:componentDidMount
以及:
useEffect(() => {
// 触发这个逻辑...
}, [])
当Offscreen
组件从「活动」变为「失活」时,会触发componentWillUnmount
与:
useEffect(() => {
// ...
return () => {
// 触发这个逻辑...
}
}, [])
所以,这些曾经被认为在组件生命周期中只会触发一次的方法,由于Offscreen
,在未来可能会多次触发。
这也是React
提前在StrictMode
中加上Strict Effect
规则的原因。
就像渣男变心前都会有些反常的举动。
React18是真的挑战
不管是Offscreen
还是Concurrent Mode
,可以预见随着v18
的到来,React
会更强大,相应的学习曲线会更陡峭。
这既是机遇,也是挑战。
千万别等变化一股脑到眼前时再埋怨:
你个渣男,当初说好一心一意只会触发一次,现在为了妖艳新特性,背叛我们的诺言。
到那时React
只会拍拍屁股转身,留下不羁的背影:
参考资料
[1]StrictMode文档: https://zh-hans.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects