React 18 的相关分享——从项目使用角度来说

安装依赖

如果是新项目的话,直接搭建安装即可。如果是已有的项目,想要升级 React 18,需要更新以下几个依赖:

  • react-scriptsyarn add react-scripts(需要 node 14 版本及以上的,如果存在必须使用低版本的项目,推荐使用 nvm 做好 node 包版本管理)
  • react-domyarn add react react-dom @types/react  @types/react-dom
  • craco-lessyarn add craco-less
  • antdyarn add antd

React 18 更新最重要的概念——并发

并发是什么?

需要注意,并发不是并行,二者的区别在于:并行是同一时刻可以执行多个任务,并发是同一时刻只能执行一个任务,但一个时段内可以执行多个任务,每个任务的执行都是可中断的。

举个通俗的例子:

  • 你正在吃饭,这时电话响了,你决定吃完饭再去接电话。
  • 你正在吃饭,这时电话响了,你决定先停下吃饭去接电话,结束后再继续吃饭。(并发)
  • 你正在吃饭,这时电话响了,你决定一边吃饭一边接电话。(并行)

看一下官方的解释:

Concurrency is not a feature, per se. It’s a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time. You can think of concurrency as an implementation detail — it’s valuable because of the features that it unlocks. React uses sophisticated techniques in its internal implementation, like priority queues and multiple buffering. But you won’t see those concepts anywhere in our public APIs.

可以看得出,并发是一种后台机制,React 18 的很多新特性都是在此基础上完成的,其关键在于渲染可中断。

为什么需要并发?

js是单线程语言,如果有一个耗时的任务占据线程,就会阻塞后续的任务。如果这时用户产生了点击行为,由于线程阻塞,导致没有及时响应,就会大大降低用户的体验。

React 18 给出了解决方案:并发。给每个更新分配一个优先级,用来区分紧急程度,多个更新存在的情况下,优先执行紧急的更新,非紧急更新可以被中断甚至放弃。比如长列表的渲染是不会阻塞用户的输入或者点击操作,从而提升用户体验。

在开发过程中,我们并不需要了解任何关于并发的概念,React 18 提供的 API 中也不会有相关描述,感兴趣的可以自行了解并发的原理及实现过程。

React 18 新的内容 

root

React 18 不再支持 ReactDOM.render,需要使用 createRoot。下面看一下 src/index.tsx 文件代码前后的变化。

// before
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from "react-router-dom";
import AppRouter from "./router";

ReactDOM.render(
    <Router>
      <AppRouter/>
    </Router>,
    document.getElementById('root')
);


// after
import { createRoot } from "react-dom/client";
import {BrowserRouter as Router} from "react-router-dom";
import AppRouter from "./router";

const root = createRoot(document.getElementById('root'));
root.render(
    <Router>
      <AppRouter/>
    </Router>,
    document.getElementById('root')
);

注意:这是 React 18 里变动最明显的地方,如果不使用 createRoot,在运行项目时,会在控制台看到警告,并且 React 18 的新特性将不会生效。

 

Automatic Batching

React 18 将多个状态更新放在一次重新渲染中,这样大大提升了性能。

// before
useEffect(()=>{
    setCount(c=>c+1);
    setFlag(f=>!f);
    ...
    // 这里 React 会在每一个 setXXX 时都渲染一次
},[])


// after
useEffect(()=>{
    setCount(c=>c+1);
    setFlag(f=>!f);
    ...
    // 这里 React 18 仅会最后批量渲染一次
},[])

 

Suspense

Suspense 支持声明性的指定加载状态,减少 loading 状态的相关逻辑代码。

React v16 中就开始出现 Suspense,只是这个时候的 Suspense 只能用于配合 React.lazy 做动态加载的,其实项目中也用到了相关功能。

import React, { ComponentType, lazy, Suspense } from "react";
import { LoadingOutlined } from "@ant-design/icons";

const Loading = (
  <div style={{ paddingTop: "20%", textAlign: "center" }}>
    <LoadingOutlined style={{ fontSize: "48px", marginBottom: "16px" }} />
    <br />
    <span>加载中...</span>
  </div>
);

const loadable = (
  importFunc: () => Promise<{ default: ComponentType<any> }>, { fallback = null } = { fallback: Loading }) => {
  const LazyComponent = lazy(importFunc);

  return (props: Object) => (
    <Suspense fallback={fallback}>
      <LazyComponent {...props} />
    </Suspense>
  );
};

export default loadable;

React 18 扩展了 Suspense 的能力,支持 SSR,利用并发新特性,支持渲染中断,待整个 DOM 树就绪后渲染。

 

Transitions

transition 是 React 18 出现的新概念,用来区分紧急和非紧急更新,实现过渡更新,从而提升用户体验。React 18 提供了三个 API:

  • startTransition:方法(用于 hook 不可用时)。
  • useTransition: hook,返回一个带有状态标记的布尔值,一个设置为非紧急更新的方法,类似 startTransition。
  • useDeferredValue:hook,是一个延迟的状态值。

通过一个常见的场景来看一下这三者的具体用法,比如用户的输入操作,可能会同时存在紧急更新和非紧急更新,输入内容的呈现很明显是一个紧急更新,根据输入内容筛选数据往往并不需要及时反馈,我们会通过 setTimeout/debounce 来处理,这种情况下,我们就可以使用 transition 来告诉 React 哪些是紧急更新,哪些是非紧急的。

const [ value ,setInputValue ] = React.useState('')


// startTransition
const [ query ,setSearchQuery  ] = React.useState('')
const handleChange = (e) => {
    setInputValue(e.target.value)
    startTransition(()=>{
        setSearchQuery(e.target.value)
    })
}


// useTransition
const [ query ,setSearchQuery  ] = React.useState('')
const [ isPending , startTransition ] = React.useTransition()

const handleChange = (e) => {
    setInputValue(e.target.value)
    startTransition(()=>{
        setSearchQuery(e.target.value)
    })
}


// useDeferredValue
const query = React.useDeferredValue(value)

const handleChange = (e) => {
    setInputValue(e.target.value)
}
    

 

参考文献

React v18.0 ——官方文档

How to Upgrade to React 18 ——官方文档

「原理篇」你真的了解 React18 的并发吗?——掘金

React 18 Suspense 的变化——掘金

「React18新特性」深入浅出用户体验大师—transition ——掘金

posted @ 2022-08-10 14:05  shellon  阅读(654)  评论(0编辑  收藏  举报