2021年快过完了,年末,总结一下,一些技术的分享。

React 18 Alpha已经发版。主要有以下改变:

1. React 18使用:ReactDOM.createRoot

const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);

React 17使用:ReactDOM.render

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 

2. 自动批量处理(automatic batching)--为了性能考虑,如果有多个状态更新,React会统一处理,组件只会渲染一次。

react 17,直接放在事件中,会进行batching,页面只会渲染1次。

function handleClick() {
    setCount((c) => c + 1); 
    setFlag((f) => !f); 
  }

Demo: React 17 batches inside event handlers.(Notice one render per click in the console.)

react 17,用promise, setTimeout,原生事件,不会进行batching,变量更新几次,页面就会渲染几次。

function handleClick() {
    fetchSomething().then(() => {
      setCount((c) => c + 1); 
      setFlag((f) => !f); 
    });
  }

Demo: React 17 does NOT batch outside event handlers. (Notice two renders per click in the console.)

注意:对于promise, setTimeout,原生事件,可以通过使用unstable_batchedUpdates,进行batching。但这个方法不提倡,后续会淘汰

import { unstable_batchedUpdates } from 'react-dom';

unstable_batchedUpdates(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
});

 

React 18,用promise, setTimeout,原生事件,也会进行batching,页面只会渲染1次。

Demo: React 18 with createRoot batches even outside event handlers! (Notice one render per click in the console!)

 

注意:如果不想进行batching,可以使用flushSync

import { flushSync } from 'react-dom'; // Note: react-dom, not react

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  flushSync(() => {
    setFlag(f => !f);
  });
}

这个改变,对hooks没影响,对class components 有影响。

例如:对于hooks,没有影响。

function handleClick() {
  setTimeout(() => {
    console.log(count); // 0
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    console.log(count); // 0
  }, 1000)

 

例如,在react17中,对class components,可以通过原生事件,setTimeout,将异步改为同步。

handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }));

    // { count: 1, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};

 

但是,在react18中,是不起作用的。

handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }));

    // { count: 0, flag: false }
    console.log(this.state);     

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};

注意:react18中,就需要使用ReactDOM.flushSync ,强制更新。建议尽量不要这么做。

handleClick = () => {
  setTimeout(() => {
    ReactDOM.flushSync(() => {
      this.setState(({ count }) => ({ count: count + 1 }));
    });

    // { count: 1, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};

 3. React 18的SSR的新Suspense

 

4.  React 18的Suspense变化

Suspense--用于数据获取。可以“等待”目标代码加载,并且可以直接指定一个加载的界面(像是个 spinner),让它在用户等待的时候显示。fallback定义loading显示.

<Suspense fallback={<Loading />}>
  <ComponentThatSuspends />
  <Sibling />
</Suspense>

React 17 demo sandbox showing all the effects firing too early

React 17,子组件依然会渲染,会隐藏,等待数据,会消耗性能。

React 18 with createRoot demo sandbox showing effects delayed until the content is ready.

React 18,等拿到数据,子组件才做渲染。

 

5.SuspenseList--用于控制Suspense组件的显示顺序。

属性
revealOrder:Suspense加载顺序

together: 所有Suspense一起显示,也就是最后一个加载完了才一起显示全部

forwards: 按照顺序显示Suspense

backwards: 反序显示Suspense

tail:是否显示fallback,只在revealOrder为forwards或者backwards时候有效

hidden:不显示

collapsed:轮到自己再显示

 

 

 

 

 

 

 

 

import {useState, Suspense, SuspenseList} from "react";
<SuspenseList tail="collapsed">
          <Suspense fallback={<h1>loading - user</h1>}>
            <User resource={resource} />
          </Suspense>
        </ErrorBoundaryPage>

        <Suspense fallback={<h1>loading-num</h1>}>
          <Num resource={resource} />
        </Suspense>
      </SuspenseList>

 

6.startTransition--标记某个更新为transition,先短暂停留在当前页面, 只要下一个视图加载完了,再进行切换。

Concurrent模式,就是用户可以自定义更新任务优先级并且能够通知到React,React再来处理不同优先级的更新任务。Concurrent 模式减少了防抖和节流在 UI 中的需求。因为渲染是可以中断的,React 不需要人为地 延迟 工作以避免卡顿(比如使用setTimeout)。它可以立即开始渲染,但是当需要保持应用响应时中断这项工作。 

紧急更新:setState, 非紧急更新: startTransition,useTransition,useDeferredValue

如UI从一个视图向另一个视图的更新,停留在当前视图,只要下一个视图加载完了,直接切换就行。

import { startTransition } from "react";
startTransition(() => {
            refresh();
});
与setTimeout不同

startTransition出现之前,我们可以使用setTimeout来实现优化。但是现在有了startTransition基本上可以抛弃setTimeout了,原因主要有以三点:

首先,与setTimeout不同的是,startTransition并不会延迟调度,而是会立即执行,startTransition接收的函数是同步执行的,只是这个update被加了一个“transitions"的标记。而这个标记,React内部处理更新的时候是会作为参考信息的。这就意味着,相比于setTimeout, 把一个update交给startTransition能够更早地被处理。而在于较快的设备上,这个过度是用户感知不到的。

使用场景:

  • 渲染慢:如果你有很多没那么着急的内容要渲染更新。

  • 网络慢:如果你的更新需要花较多时间从服务端获取。这个时候也可以再结合Suspense

7.useTransition--在使用startTransition更新状态的时候,用户可能想要知道transition的实时情况(eg 当前加载中),这个时候可以使用React提供的hook api useTransition

import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();

 

 8.useDeferredValue--延迟更新某个不那么重要的部分。保证高优先级的任务,优先执行。

例如:当用户在输入框输入字的时候,用户应该立马看到输入框的反应,相比之下,模糊查询结果可以延迟显示。

import {useDeferredValue, useState} from "react";
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text);