一起来看 React 18 最新特性

11,378 阅读4分钟

React 18 Alpha 以及来了,并没有像 React 17 没有更新很多新特性,V18 作为 React 的下一个大版本将关注点放在了并发模式上也就是谈论了很久的(Concurrent Mode)

那么究竟都将有哪些新特性,下面让我们一起来看一看。

安装 React 18 Alpha

npm install react@alpha react-dom@alpha

新的 Root API

之前的使用中都是通过 ReactDom.render 将应用组件渲染到页面的根元素

ReactDOM.render(<APP/>,document.getElementById( root ))

React 18 提供了新的方法 通过 ReactDom.creatRoot 创建根节点对象,在通过根节点对象进行渲染。

(使用 CodeSandBox 进行测试)

// index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

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

节点自动批量重渲染

在React 17及更早版本中,当浏览器事件触发组件批量状态更新从而渲染更新时,React 不会自动将组件批量重新渲染。

但是如果使用上面提到的 V18 新 Root API,则所有状态更新都将在发生时自动批量重渲染。

如果某些关键组件不想被自动更新可以使用 ReactDOM.flushSync() 退出操作

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

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setFlag(f => !f);
  });
  // React has updated the DOM by now
}

新的 Suspense 容器组件

React 18 的更新后全面支持 Suspense ,顾名思义将暂时闲置的组件搁置起来,实现懒加载。

import "./styles.css";
import { useEffect, useState, useLayoutEffect, Suspense } from "react";
import { fetchProfileData } from "./fakeApi";

const initialResource = fetchProfileData();

function ProfilePage() {
  const [resource, setResource] = useState(initialResource);
  return (
    <>
      <Suspense
        fallback={
          <>
            <h1>Loading profile...</h1>
          </>
        }
      >
        <Sibling name="one" />
        <ProfileDetails resource={resource} />
        <Suspense fallback={<h1>Loading posts...</h1>}>
          <Sibling name="two" />
          <ProfileTimeline resource={resource} />
          <Sibling name="three" />
        </Suspense>
        <Sibling name="four" />
      </Suspense>
    </>
  );
}

function Sibling({ name }) {
  useLayoutEffect(() => {
    console.log("Layout effect Sibling", name);
    return () => {
      console.log("Layout cleanup Sibling", name);
    };
  });

  useEffect(() => {
    console.log("Effect Sibling", name);

    return () => {
      console.log("Cleanup Sibling", name);
    };
  }, [name]);

  console.log("Render sibling", name);
  return <h1>Sibling</h1>;
}

function ProfileDetails({ resource }) {
  useLayoutEffect(() => {
    console.log("Layout effect ProfileDetails");
    return () => {
      console.log("Layout cleanup ProfileDetails");
    };
  });

  useEffect(() => {
    console.log("Effect ProfileDetails");
    return () => {
      console.log("Cleanup ProfileDetails");
    };
  });
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline({ resource }) {
  const posts = resource.posts.read();
  useLayoutEffect(() => {
    console.log("Layout effect ProfileTimeline");
    return () => {
      console.log("Layout cleanup ProfileTimeline");
    };
  });

  useEffect(() => {
    console.log("Effect ProfileTimeline");
    return () => {
      console.log("Cleanup ProfileTimeline");
    };
  });

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

export default ProfilePage;

当数据未加载完成,显示 Loading 组件,加载后显示完整组件。

代码源于romantic-architecture-ht3qi

关于 Suspense API 也尚未稳定,不建议使用在生产环境

Suspense List

Suspense List 为 Concurrent Mode 中的提案 reactjs.org/docs/concur…

Suspense List 作为 Suspense 的容器组件通过编排这些组件向用户显示的顺序,帮助协调许多可以挂起的组件。

<SuspenseList revealOrder="forwards">
  <Suspense fallback={'Loading...'}>
    <ProfilePicture id={1} />
  </Suspense>
  <Suspense fallback={'Loading...'}>
    <ProfilePicture id={2} />
  </Suspense>
  <Suspense fallback={'Loading...'}>
    <ProfilePicture id={3} />
  </Suspense>
  ...
</SuspenseList>
  • revealOrder (forwards, backwards, together) 定义了 SuspenseList 子组件应该显示的顺序。

  • tail (collapsed, hidden) 指定如何显示 SuspenseList 中未加载的项目。

  • 默认情况下,SuspenseList 将显示列表中的所有 fallback。

  • collapsed 仅显示列表中下一个 fallback。hidden 未加载的项目不显示任何信息。

  • 请注意,SuspenseList 只对其下方最近的 Suspense 和 SuspenseList 组件进行操作。它不会搜索深度超过一级的边界。不过,可以将多个 SuspenseList 组件相互嵌套来构建栅格。

startTransition API

这是 V18 引入的新 API,这有助于保持当前的网页响应,并且能够同时进行计算量大复杂度高的的非阻塞UI更新。 以前我们可能会自己去加一些防抖这样的操作去人为的延迟过滤数据的计算和渲染。新的 startTransition API 可以让我们把响应数据标记成 transitions 状态延迟处理。

官方工作组两个应用场景提出了:

  • 慢速渲染:React 需要执行大量计算,以便过渡UI来显示结果。(如搜索引擎的关键词联想)
  • 慢速网络:React 正在等待来自网络的某些数据。这种用例与 Suspense 紧密集成。(懒加载)

startTransition API 可以让开发者显式的指定那个UI渲染的优先级更高,哪些需要实时更新哪些需要延迟更新

(使用 CodeSandBox 进行测试)

// APP.js
import "./styles.css";
import { useState, startTransition } from "react";

export default function App() {
  let [value, setValue] = useState(0);

  const onChange = (event) => {
    startTransition(() => {
      setValue(event.target.value);
      console.log(event.target.value);
    });
  };

  return (
    <div className="App">
      <input value={value} onChange={onChange} />
      <div>{value}</div>
    </div>
  );
}

所有在 startTransition 回调中的更新都会被认为是非紧急处理,如果出现更紧急的更新(比如用户又输入了新的值),则上面的更新都会被中断,直到没有其他紧急操作之后才会去继续执行更新。

实际测试时发现好像对于中文输入法不是特别友好但目前没有找到合适的解决方案。

useDeferredValue

useDeferredValue 允许变量延迟更新, API 还未稳定,当前用法为

import "./styles.css";
import { useState, useDeferredValue } from "react";

export default function App() {
  let [value, setValue] = useState(0);
  const deferredValue = useDeferredValue(value, { timeoutMs: 2000 });

  return (
    <div className="App">
      <div>{deferredValue}</div>
      <button onClick={()=>{setValue(deferredValue+1)}}>click me</button>
    </div>
  );
}

总结

React 18 发布计划 React 18 官方介绍(github.com/reactwg/rea… API useDeferredValue、 还没 released ,我们下次再用,下面是 React 18 的发布时间表:

  • React 18 Alpha 版本:现在就能用
  • 公开的 Beta 版:至少在 Alpha 版本后的几个月
  • RC 版本:至少在 Beta 版发布后的几周
  • 正式版:至少在 RC 版本发布之后的几周

参考链接:

Everything New in React 18

【第一批吃螃蟹】试用 React 18 !

解读官方博客:React18真的来了