react 18新特性
2022年6月8日 • ... • ☕️ 6 min read
react 18版本已经发布,最近在考虑项目升级,所以做了一些功课,大概内容和注意事项。
升级模式
和以往大版本一样,react采用渐进升级的方式,初期会有一些警告,提供迁移路线和一些milestone。一般旧的api会保留很多版本,以保证用户使用。
需迁移的api
ReactDOM.render
使用新的createRoot创建方式,为了支持React18的新特性(主要是concurrency和hydrate方面)
// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);// Before
unmountComponentAtNode(container); // 卸载
// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // ts使用:createRoot(container!)
root.render(<App tab="home" />);
root.unmount(); // 卸载
和旧版本api相比,删除了callback参数(因为使用suspense会有问题)
目前用旧版(开发模式)会有警告,原有功能不受影响:
Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more:
Typescript定义
children需要额外定义。意味着在写props的type定义时,children需要单独写出来了。
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
新特性
concurrency(并发)
concurrency依赖于Fiber结构,之前一直是实验特性。到react 18,终于发布了稳定版本。
它可以解决一些耗时任务和用户交互竞争CPU的时候,造成页面卡顿、响应不及时的问题。
另一方面,可以解决某些情况下,希望延迟渲染数据的需求(例如切换页面,还没有加载代码的时候,看到白页或者loading都不太合适,希望停留在前一个页面)。
推荐阅读:React 的 Concurrent Mode 是否有过度设计的成分?
官方提供了一系列工具:
1、suspense
在之前,suspense的功能之一配合React.lazy进行代码分割用的。提供了fallback来达到显示loading状态的目的。r18中,依然是帮助提升用户体验。主要作用还是读取异步的state,并根据中间状态做一些显示。
与之前不同的,r18可以把Suspense放在最外层,监听任意深层次组件的异步变更,并做出反应(例如骨架图)。
2、transition(useTransition,startTranstion)
const [isPending, startTransition] = useTransition();
startTransition(() ⇒ {
setSomeData(someData);
// …
});
返回isPending状态,startTransition用于包装状态变更函数(setState),使用它,可以告诉React,这个函数的状态更新不着急使用。
并且,通过isPending的判断,可以达到和suspense的fallback类似的效果。
场景(页面跳转,需要loading内容):
prev: https://codesandbox.io/s/sparkling-field-41z4r3
now: https://codesandbox.io/s/serverless-fast-isjkvt
配合suspense可以看到,useTransition之后,并没有造成loading状态(即fallback),显示的是完整的数据页面,而不是显示一个中间状态,然后继续加载数据。
3、useDeferredValue
和transition功能类似。但是主要应用在无法使用useTransition的地方(例如当前代码段无法控制的state,例如来自父组件)。该api是备选方案,应优先选择使用transition。
自动批量更新(Automatic Batching)
批量更新是指,例如同时set两个state,会在commit的最后只进行一次渲染。
react 18之前,只有事件处理函数里的set方法,会被批处理。这意味着,promise,setTimeout原生事件处理函数,批处理是无效的。
r18对这个添加了支持。
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// r18之前,会render两次,每个state update都会有一次
// r18只用render一次
}, 1000);
当然这个特性也可以取消:
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
flushSync(() => {
setFlag(f => !f);
});
// React会分别更新两个state
}
组件可以返回undefined
之前组件返回undefined会有警告。
问题在于有时候忘记返回或者显式返回undefined区别不太明显。另外如果使用Typescript,一些可以为undefined的组件返回,就要伴随组件一层一层加下去。
见:Update to allow components to render undefined
移除setState on unmounted component警告
之前,为了提醒内存泄漏风险,提示了”Can’t perform a React state update on an unmounted component.”,例如有未完成的promise。官方发现,其实state变化还好,但是为了处理这个警告,做了好多其他工作,代码看起来更诡异了(比如cleanup乱七八糟,额外用state控制这个promise等)。见 remove the “setState on unmounted component” warning。
suspense的fallback变为可选
<Suspense fallback={<Loading />}> // 之前:必须有fallback
<Suspense> // 现在:不必要
<Page />
</Suspense>
</Suspense>
详情见:Update to how we handle null or undefined Suspense fallbacks
三方库使用的api
useId
用于生成唯一Id。注意不能用于在list里生成keys。见Intent to ship: userId
useSyncExternalStore
是一个新的 Hook,允许外部状态管理器,强制立即同步更新,以支持并发读取。这个新的 API 推荐用于所有 。 React 外部状态管理库。详情见 useSyncExternalStore overview post、useSyncExternalStore API details。
useInsertionEffect
是一个新的 Hook,它可以解决 CSS-in-JS 库在渲染中动态注入样式的性能问题。除非你已经构建了一个 CSS-in-JS 库,否则我们不希望你使用它。这个 Hook 执行时机在 DOM 生成之后,Layout Effect 执行之前。更多信息可见 Library Upgrade Guide for style。