一、发版回顾
2017.9 React16加入Fiber架构;
2020.10 React17发布,作为垫脚石版本,谨慎地探索新特性,支持渐进式升级;
2022.3 React18发布,正式开启并发更新。
虽然两三年一版本,看起来更新很慢,但影响特别大,中间的提案过程很多,考虑得比较谨慎。
二、变更型新特性
-
createRoot
- ReactDOM.render换成ReactDOM.createRoot,不换的话会有警告
- 去掉了callback,避免使用Suspense时出现问题
- 这个新入口允许使用并发功能
- 对应该的unmountComponentAtNode需换成root.unmount
// React 17
const root = document.getElementById('root');
ReactDOM.render(<App />, root, () => { console.log('执行 callback') })
// React 18
const AppWithCallback = () => {
useEffect(() => { console.log('执行 callback'); }, [])
return <App />
}
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(<AppWithCallback />)
-
SSR hydrateRoot
- hydrate替换为hydrateRoot
- 支持服务端Suspense/流式SSR,优化了相关API
// 之前
const container = document.getElementById('app')
ReactDOM.hydrate(<App />, container)
// 之后
const container = document.getElementById('app');
const root = ReactDOM.hydrateRoot(container, <App />);
// 和createRoot不一样,此处不再需要root.render()
-
Strict Mode
- 严格模式下每个组件会两次渲染,以便观察潜在问题;安装React DevTools后,第二次渲染的日志信息将显示为灰色
- 这个模式帮助在开发过程中发现与并发相关的错误,检查可重用状态和不合适的写法,比如state不变,组件卸载重载
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effect setup code runs
* Effect setup code runs
-
自动批处理
- 将多个状态更新合并与一次重新渲染,以获得更好的性能
- React 18之前,React 只能在组件的生命周期函数或者合成事件函数中进行批处理。默认情况下,Promise、setTimeout 以及原生事件中是不会进行批处理的。如果需要使用批处理,可以用 unstable_batchedUpdates,但它不是正式API
- React 18之后,在并发模式下,任何情况都会自动执行批处理
- 如果需要退出批量更新,可以使用ReactDOM.flushSync手动退出,即强制要求立即触发重新渲染,这样可以精准控制更新
- 更详细示例可查看:github.com/reactwg/rea…
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // 状态更新,不触发渲染
setFlag(f => !f); // 状态更新,不触发渲染
// 结束的时候,才触发一次重新渲染,即之前的更新批量的做一次重新渲染
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
function handleClick() {
flushSync(() => {
setCount(2);
});
// 会在 setCount(2) 并 render 之后再执行 setCount(3)
setCount(3);
}
三、新的并发特性
-
并发模式
并发模式(Concurrent Mode)16就有了,17通过试验性API开启,做了大量渐进升级。
并发模式不是一个功能,而是一种底层设计。
并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,以保证最高的快速响应。
16的Fiber架构之前,是不可中断的递归方式更新。之后,使用了可中断的遍历方式更新,使并发更新成为可能。
但是所谓并发模式,最终要落地的是,自动批处理、以及一些并发更新特性。并发特性包括:useTransition、useDeferredValue,可以分开启用。18通过并发特性的开关,来启用并发更新。
总结一下,开启并发模式,即可开启自动批处理;在并发模式下,使用相应的并发特性,才能开启并发更新。
这三个词是渐进升级过程的产物,最终都说的一个意思,让应用做到响应性更好。
const App = () => {
const [count, updateCount] = useState(0);
const [isPending, startTransition] = useTransition();
const onClick = () => {
// 使用了并发特性useTransition
startTransition(() => {
// 本次更新是并发更新
// 如果updateCount没有作为startTransition的回调函数执行,那么updateCount将触发默认的同步更新。
updateCount((count) => count + 1);
});
};
return <h3 onClick={onClick}>{count}</h3>;
};
-
useTransition
startTransition能在大量的任务下也能保持 UI 响应,比如在不同视图之间进行导航切换,不阻止用户输入实现原理是,startTransition 回调中的 setState 触发的渲染被标记为不紧急渲染,这些渲染能被其他紧急渲染抢占- 这一
API很可能会由各种Router实现,再作为一个配置项开放给开发者 - 详情参考:github.com/reactwg/rea…
-
useDeferredValue
- 返回一个延迟响应的值,通常用于用户输入要立即渲染场景,或要等待数据获取时,保持接口的可响应性。
useDeferredValue内部实现与useTransition一样,都是标记了延迟更新任务useTransition是把更新任务变成了延迟更新任务,而useDeferredValue是产生一个新的值,这个值作为延时状态;一个用来包装方法,一个用来包装值- 相比于debounce的手动设定延时值,useDeferredValue 延时值是高优渲染耗时值,在性能好的机器上比较小,这样能快速响应,在性能不好的机器上比较大,这样能适当延迟、避免执行阻塞。即,它是一个自适应的最佳的值,比手动设置dobounce的响应性更优。
四、新的Hook API
定义了一些新的 API,以满足三方库在并发模式下特定场景的诉求。
比如styles管理、外部状态管理、可访问性(accessibility)等场景。
-
useId
支持在客户端和服务端生成唯一ID,主要为解决流式SSR返回的 HTML 片段无序的问题。
-
useSyncExternalStore
允许外部状态管理器,强制立即同步,以支持并发读取。
-
useInsertionEffect
在DOM生成之后、LayoutEffect执行之前执行,用于解决CSS-in-JS库在渲染中动态注入样式的性能问题。