大家好,我卡颂
React18
正式版曾经公布一段时间了,如果你降级到 v18
,且仍应用ReactDOM.render
创立利用,会收到如下报警:
粗心是说:v18
应用 createRoot
而不是 render
创立利用,如果你仍应用 render
创立利用,那么利用的行为将同 v17
一样。
React
团队之所以有底气让大家都降级到v18
,应用createRoot
,是因为他们作出了承诺:
粗心是说:如果你降级到 v18
,只有不应用 并发个性 (比方useTransition
),React
会和之前版本体现统一(更新会同步、不可中断)
明天这篇文章想说的是:某些状况下,上述说法是谬误的。
欢送退出人类高质量前端框架群,带飞
不说废话,上示例
示例中有 a
、b
两个状态,首次渲染完 2 秒后会触发 a
、b
更新。
其中触发 b
更新的形式比拟非凡:模仿点击,间接触发 b
更新:
function App() {const [a, setA] = useState(0);
const [b, setB] = useState(0);
const BtnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {setTimeout(() => {setA(9000);
BtnRef.current?.click();}, 2000);
}, []);
return (
<div>
<button
ref={BtnRef}
onClick={() => setB(1)}>
b: {b}
</button>
{Array(a).fill(0).map((_, i) => {return <div key={i}>{a}</div>;
})}
</div>
);
}
残缺示例地址
当初咱们有两种挂载 <App/>
的形式。
v18
之前的形式:
const rootElement = document.getElementById("root");
// v18 之前创立利用的形式
ReactDOM.render(<App/>, rootElement);
v18
提供的形式:
const root = ReactDOM.createRoot(rootElement);
// v18 创立利用的形式
root.render(<App />);
为了看清这两者的区别,有两种形式:
- 调大
setA(9000)
中的值,使页面渲染更多项。页面渲染时卡顿越显著,渲染程序的差别越显著
setTimeout(() => {setA(9000);
BtnRef.current?.click();}, 2000);
- 在
react-dom.development.js
的commitRootImpl
办法中打断点
这个办法是 React
渲染时调用的办法,在这里打断点能够看出页面渲染的程序。
对于 ReactDOM.render
创立的利用,触发更新后渲染程序如下:
首先:
其次:
对于 ReactDOM.createRoot
创立的利用,触发更新后渲染程序如下:
首先:
其次:
渲染程序显然是变了,这和 React
文档里的说法是相悖的。
背地的起因是什么呢?
更新的优先级,无处不在
先解释下示例中的 b
为什么采纳 触发 onClick 事件 的形式间接触发更新:
BtnRef.current?.click();
这是因为:不同形式触发的更新有不同 优先级 ,onClick 回调
中触发的更新是最高优的,即 同步优先级。
那么问题来了,v18
不应用并发个性,所有更新不都该是 同步、不可中断 么?
这话是没错,更新自身是 同步、不可中断 的。然而更新是须要调度的。
在示例中,如果采纳 ReactDOM.createRoot
创立利用,那么触发更新时的优先级如下:
setTimeout(() => {
// 触发更新,优先级为“默认优先级”setA(9000);
// 触发更新,优先级为“同步优先级”BtnRef.current?.click();}, 2000);
接下来 React
的执行流程如下:
a
触发更新,优先级为“默认优先级”- 调度
a
的更新,优先级为“默认优先级” b
触发更新,优先级为“同步优先级”- 调度
b
的更新,优先级为“同步优先级” - 此时发现曾经有个更新在调度(
a
的更新),且优先级更低(默认优先级 < 同步优先级) - 勾销
a
的更新的调度,转而开始调度b
的更新 - 调度流程完结,开始同步、不可中断的执行
b
的更新 b
对应更新渲染到页面中- 此时发现还有一个更新(
a
的更新),调度他 - 调度流程完结,开始同步、不可中断的执行
a
的更新 a
对应更新渲染到页面中
可见,只有采纳 ReactDOM.createRoot
创立利用,那么 优先级 的影响就会始终存在,与 应用了并发个性 的区别是:
- 只有 默认优先级 与同步优先级
- 优先级只会影响调度,不会中断更新的执行
老版 React 的历史包袱
那么采纳 ReactDOM.render
创立的利用执行程序又是怎么一回事呢?
记不记得一道经典(且毫无意义)的 React
面试题:React
的更新是同步还是异步的?
上面两种状况,a
打印的后果是 1
么?
// 状况 1
onClick() {this.setState({a: 1});
console.log(a);
}
// 状况 2
onClick() {setTimeout(() => {this.setState({a: 1});
console.log(a);
})
}
其中,状况 2 中 a
打印后果是1
。
之所以会有这种状况,是 React
晚期实现 批处理
时的瑕疵造成的,并不是什么无意为之的个性。
当 React
应用 Fiber
架构重构后,齐全能够躲避这个瑕疵。但为了与老版本行为保持一致,刻意实现成这样。
所以,在咱们的示例中,这两个更新不会受到 优先级 的影响,但会受到 为了兼容老版本 造成的影响:
setTimeout(() => {setA(9000);
BtnRef.current?.click();}, 2000);
React
的执行流程如下:
a
触发更新,因为是在setTimeout
中触发的,所以会同步执行后续更新流程a
对应更新渲染到页面中b
触发更新,因为是在setTimeout
中触发的,所以会同步执行后续更新流程b
对应更新渲染到页面中
总结
React
作为一款保护了快 10 年的框架,在经验重大版本更新后要放弃框架行为前后一致,实属不易。
更新程序的变动对个别利用影响不大。
然而,如果你的利用依赖更新后 页面中以后的值 作出后续判断,那么须要留神降级到 v18
后的这些轻微变动。