React18新特性

Render Api

为了更好的管理root节点React 18 引入了一个新的 root API,新的 root API 还支持 new concurrent renderer(并发模式的渲染),它允许你进入concurrent mode(并发模式)。

// React 17
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.render(<App />, root);

// React 18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.createRoot(root).render(<App />);

setState 批处理

在React18之前,只有在React事件处理函数中会进行批处理更新

以下代码在18及18之前的版本,都只会打印一次render

import React, { useState } from 'react';

const BatchSetState = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  console.log('render');

  return (
    <div>
      <button
        onClick={() => {
          setCount1(count1 + 1);
          setCount2(count2 + 1);
        }}
      >
        点击
      </button>
    </div>
  );
};

export default BatchSetState;

而在promise, setTimeout, 原生事件处理函数其他任何事件内都不会批处理

以下代码,在18的版本中只会打印一次render, 而在之前的版本,会打印两次

import React, { useEffect, useState } from 'react';

const BatchSetState2 = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  // useEffect(() => {
  //   document.body.addEventListener('click', () => {
  //     setCount1((count) => count + 1);
  //     setCount2((count) => count + 1);
  //   });
  //   // 在原生js事件中不会进行批处理
  // }, []);

  console.log('render');

  return (
    <div>
      <button
        onClick={() => {
          setTimeout(() => {
            setCount1(count1 + 1);
            setCount2(count2 + 1);
          }, 100);
        }}
      >
        点击
      </button>
    </div>
  );
};

export default BatchSetState2;

flushSync

批处理是一个破坏性改动,想退出批量更新,可以用flushSync

import React, { useState } from 'react';
import { flushSync } from 'react-dom';

const FlushSync = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  console.log('render');

  return (
    <div>
      <button
        onClick={() => {
          flushSync(() => {
            setCount1(count1 + 1);
          });
          flushSync(() => {
            setCount2(count2 + 1);
          });
        }}
      >
        点击
      </button>
    </div>
  );
};

export default FlushSync;

效果如下,会打印两次render

图1

需要注意的是,一个flushSync里的多个setState,还是会批处理更新

import React, { useState } from 'react';
import { flushSync } from 'react-dom';

const FlushSync2 = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  console.log('render');

  return (
    <div>
      <button
        onClick={() => {
          flushSync(() => {
            setCount1(count1 + 1);
            setCount2(count2 + 1);
          });
        }}
      >
        点击
      </button>
    </div>
  );
};

export default FlushSync2;

效果如下

图二

startTransition

startTransition,主要为了能在大量的任务下也能保持 UI 响应。这个新的 API 可以通过将特定更新标记为“过渡”来显著改善用户交互,简单来说,就是被 startTransition 回调包裹的 setState 触发的渲染被标记为不紧急渲染,这些渲染可能被其他紧急渲染所抢占。

import React, { useState, useEffect, useTransition } from 'react';

const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  const [isPending, startTransition] = useTransition();
  useEffect(() => {
    // 使用了并发特性,开启并发更新
    startTransition(() => {
      setList(new Array(10000).fill(null));
    });
  }, []);
  return (
    <>
      {list.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};

export default App;

useDeferredValue

返回一个延迟响应的值,可以让一个state 延迟生效,只有当前没有紧急更新时,该值才会变为最新值。useDeferredValuestartTransition 一样,都是标记了一次非紧急更新。

从介绍上来看 useDeferredValueuseTransition 是否感觉很相似呢?

  • 相同:useDeferredValue 本质上和内部实现与 useTransition 一样,都是标记成了延迟更新任务。
  • 不同:useTransition 是把更新任务变成了延迟更新任务,而 useDeferredValue 是产生一个新的值,这个值作为延时状态。(一个用来包装方法,一个用来包装值)

所以,上面 startTransition 的例子,我们也可以用 useDeferredValue 来实现:

import React, { useState, useEffect, useDeferredValue } from 'react';

const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  useEffect(() => {
    setList(new Array(10000).fill(null));
  }, []);
  // 使用了并发特性,开启并发更新
  const deferredList = useDeferredValue(list);
  return (
    <>
      {deferredList.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};

export default App;

参考文档


React18新特性
http://yellowcan.top/2023/05/07/react18-xin-te-xing/
作者
黄罐头
发布于
2023年5月7日
许可协议