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放在最外层,监听任意深层次组件的异步变更,并做出反应(例如骨架图)。

更多:Suspense for Data Fetching

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),显示的是完整的数据页面,而不是显示一个中间状态,然后继续加载数据。

更多:usetransition

3、useDeferredValue

和transition功能类似。但是主要应用在无法使用useTransition的地方(例如当前代码段无法控制的state,例如来自父组件)。该api是备选方案,应优先选择使用transition

更多:usedeferredvalue

自动批量更新(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 postuseSyncExternalStore API details

useInsertionEffect

是一个新的 Hook,它可以解决 CSS-in-JS 库在渲染中动态注入样式的性能问题。除非你已经构建了一个 CSS-in-JS 库,否则我们不希望你使用它。这个 Hook 执行时机在 DOM 生成之后,Layout Effect 执行之前。更多信息可见 Library Upgrade Guide for style

#react

SideEffect is a blog for front-end web development.
Code by Axiu / rss