React 18 新特性

1,456 阅读3分钟

React 18 alpha版已经发布了,新特性和新的API聚焦在用户体验和性能提升,一起来看看吧~

安装

npm install react@alpha react-dom@alpha

Root API

  • Leacy root API: ReactDOM.render()
  • New root API:ReactDOM.createRoot()

差别

旧 root API

import React from 'react';
import ReactDOM from 'react-dom';

const container = document.getElementById('root') 

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

新 root API(React 18)

import * as ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app');

// 创建个根节点
const root = ReactDOM.createRoot(container);

// 初始渲染: 渲染元素到根节点
root.render(<App tab="home" />);

// 更新时, 不需要再传递容器了
root.render(<App tab="profile" />);

hydration

hydration 函数移动到了 hydrateRootAPI
之前:

import ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app');

ReactDOM.hydrate(<App tab="home" />, container);

之后:

import ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app');

// 创建并渲染 hydration.
const root = ReactDOM.hydrateRoot(container, <App tab="home" />);

// 之后可以更新
root.render(<App tab="profile" />);

render callback

在旧 root API,render接受一个回调

import ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app');

ReactDOM.render(container, <App tab="home" />, function() {
  // 初始渲染或更新后调用
  console.log('rendered').
});

New root API

import * as ReactDOM from 'react-dom';
import App from 'App';

const rootElement = document.getElementById('app');

ReactDOM.createRoot(rootElement).render(<App callback={() => console.log("renderered")} />);
// 或者
ReactDOM.createRoot(rootElement).render(<App />);
requestIdleCallback(callback);
// 或者
ReactDOM.createRoot(rootElement).render(<App />);
setTimeout(callback, 0);

Automatic batching

批量更新是指多个state,而不是一个state多次更新。

差别

之前:

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 两个属性更新了,但React只渲染一次(这个就是批量更新)
  }
  
  function handleClick2() {
    fetchSomething().then(() => {
      // React 17 及之前不会批量更新
      setCount(c => c + 1); // Causes a re-render
      setFlag(f => !f); // Causes a re-render
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

React 18之前仅仅只有React事件处理函数中是批量更新的,promise,setTimeout,原生事件和其它事件中都不是批量更新。

而React18中,在promise,setTimeout,原生事件和其它事件中也都会批量更新了。
之后:

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() => {
      // React 18 并且使用 New root API
      setCount(c => c + 1);
      setFlag(f => !f);
      // 只会渲染一次
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

不想批量更新?

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React 现在更新DOM了
  flushSync(() => {
    setFlag(f => !f);
  });
  // React 现在更新DOM了
}

startTransition()

React中如果更新的DOM树很大,ODM比对要花很多时间,容易造成页面失去响应和卡顿。startTransition()可以来解决这个问题。 举一个例子:在输入框中输入文字,同时根据输入的文字查询数据展示提示列表。

// 紧迫: 显示输入文字
setInputValue(input);

// 不紧迫: 显示查询结果
setSearchQuery(input);

这时如果结果很多,同步渲染结果列表,可能会让输入框卡顿。 React 18 之前所有的更新都是同时渲染的。

// 紧迫: 显示文字
setInputValue(input);

// 标记state
startTransition(() => {
  // Transition: 显示结果
  setSearchQuery(input);
});

意味着startTransition标记的state更新可以被更加紧迫的更新打断。避免页面卡顿。

Suspense

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

http请求会阻塞html的解析,把<Comments/>组件写到<Suspense/>中,React可以跳过请求评论数据,继续解析剩余的html。这样不会阻塞。

useDeferredValue

不需要紧迫显示的text可以延迟一定时间,降低更新优先级

const deferredValue = useDeferredValue(value, { timeoutMs: 3000 });



React 18 所有更新讨论列表