梳理 React 18 的新特性

React 18 推出了一些令人兴奋的新改进。一年前宣布 React 18 时,团队承诺逐步采用策略。 现在,一年后,可以应用程序升级到最新版本。

总而言之,它还带来了开箱即用的性能改进,包括默认更多的批处理,这消除了在应用程序或库代码中手动批处理更新的需要。

本文一起深入了解 React 18 带来的一些最重要的新变化。

使用 createRoot 替换 render

createRoot启用 React 18 中的并发功能。若不使用它,应用程序将像在 React 17 上一样运行。

/** Before */
import { render } from "react-dom";
 
const container = document.getElementById("app");
render(<App />, container);
 
/** After */
import { createRoot } from "react-dom/client";
 
const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);

 为确保您正确迁移应用程序,请尝试启用严格模式:

import React from "react";
import { createRoot } from "react-dom/client";
 
function App() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <Content />
          <SignUpForm />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}
 
const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);

此外,如果使用 hydrate 进行 “hydration” 的服务端渲染,可以升级到 hydraRoot: 

/** Before */
import { hydrate } from "react-dom";
const container = document.getElementById("app");
hydrate(<App />, container);

/** After */
import { hydrateRoot } from "react-dom/client";
const container = document.getElementById("app");
const root = hydrateRoot(container, <App />);
/** 与 createRoot 不同的是这里不需要单独的 root.render() 调用。*/

自动批处理

自动批处理意味着 React 现在将批处理您在组件中进行的更新。 批处理可以防止不必要的组件渲染。

/** Before: 只有 React events 会被批处理 */
setTimeout(() => {
  /** React 将渲染两次,每次状态更新一次(无批处理) */
  setSize((oldSize) => oldSize + 1);
  setOpen((oldOpen) => !oldOpen);
}, 1000);
 
/** After: 在 timeouts、promise、native 事件处理程序或任何其他事件被批处理 */
setTimeout(() => {
  /** React 只会在最后重新渲染一次(即批处理)*/
  setSize((oldSize) => oldSize + 1);
  setOpen((oldOpen) => !oldOpen);
}, 1000);

前提是使用  createRoot  而不是 render时。 如果组件中不需要自动批处理,可以随时使用 flushSync 选择退出:

import { flushSync } from "react-dom";
 
function handleSubmit() {
  /** React 将渲染两次,每次状态更新一次(无批处理) */
  flushSync(() => {
    setSize((oldSize) => oldSize + 1);
  });
  flushSync(() => {
    setOpen((oldOpen) => !oldOpen);
  });
}

对 setCounter 和 setFlag 的调用将立即尝试更新 DOM,而不是一起进行批处理。仅此一项新功能就可以改变应用程序的性能。只需更改为 createRoot 挂载应用程序即可使用。

Transitions

React 18 引入了一个新的 transition API。 transition是 React 中的一个新概念,用于区分紧急和非紧急更新。

  • 紧急更新是反映直接交互的更新,例如键入、单击、按下等。
  • 转换更新以非紧急方式将 UI 从一个视图转换到另一个视图。

比如一个具有搜索功能的页面。 将文本添加到输入字段后,希望看到该文本立即显示在那里。 这是一个紧急更新。 但是,当您键入时,立即显示用户搜索结果并不紧迫。 相反,开发人员通常会在显示搜索结果之前对用户的输入进行去抖动或限制。因此,在输入字段中输入或单击过滤器按钮是一项紧急更新。 显示搜索结果不是紧急更新,它被视为过渡更新。 也可以使用名为 useTransition 的 hook 来获取过渡的标志,如下所示:

import { startTransition } from "react";
 
/** 紧急:显示输入的内容 */
setInputValue(newInputValue);

/** 将内部的任何状态更新标记为 transitions 并将它们标记为非紧急 */
startTransition(() => {
  /** transition:显示结果 */
  setSearchQuery(newInputValue);
});

包含在 startTransition 中的更新被视为非紧急更新,如果出现更紧急的更新(如点击或按键),则 startTransition 中的更新会被中断。假设转换被用户打断(例如,通过连续键入多个字符)。 在这种情况下,React 将丢弃未完成的陈旧渲染工作,仅渲染最新更新。

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);
 
  function handleClick() {
    startTransition(() => {
      setCount((oldCount) => oldCount + 1);
    });
  }
 
  return (
    <div>
      <span>当前次数: {count}</span>
 
      {isPending && <Spinner />}
 
      <button onClick={handleClick}> +1 </button>
    </div>
  );
}

这个过程中,React 将做三件事情:

  • yielding:每 5 毫秒,React 将停止工作以允许浏览器执行其他工作,例如运行 Promise 或触发事件。React 已经将它需要做的所有事情分解成更小的工作,并且足够聪明,可以暂停并让浏览器处理待处理的事件(这被称为“yielding”)。
  • 中断:当安排了一个新的紧急更新,React 会停止处理待处理的结果(因为它们已经过时了)。React 切换到渲染紧急更新,当紧急工作完成后,才返回判断是否渲染 transition 更新。这被称为“中断”。
  • 跳过旧结果:React 所做的就是跳过旧的工作。当它从中断中恢复时,它将从头开始渲染最新的值。这意味着 React 只在用户实际需要看到渲染的 UI 上工作,而不是旧状态。

服务端 Suspense

Suspense 现在在服务器上可用。 以前,它可以在客户端使用 React.lazy 进行代码拆分。 但是现在,可以在组件 “Suspense” 时使用某种占位符。Suspense 意味着组件还没有准备好渲染 —— 它可能缺少数据或代码。如果使用 Suspense,服务器上的慢速渲染组件将不再阻碍整个页面。如果一个组件 Suspense,它上层最近的 Suspense 组件会“捕获”它,无论中间有多少组件。

<Suspense fallback={<PageSkeleton />}>
  <LeftColumn>
    <Suspense fallback={<LeftColumnSkeleton />}>
      <Comments />
      <Photos />
    </Suspense>
  </LeftColumn>
  <RightColumn>
    <ProfileHeader />
  </RightColumn>
</Suspense>

在上面的例子中,如果 ProfileHeader Suspense,那么整个页面将被 PageSkeleton 替换。但是,如果 Comments 组件或 Photos组件 Suspense,它们都将被 LeftColumnSkeleton 替换。

这使得可以根据可视化 UI 设计的粒度安全地添加和删除 Suspense 边界,而不必担心可能依赖于异步代码和数据的组件。可以在关于 SSR Suspense 的详细 GitHub 讨论中了解更多信息。

新的 Hooks

useId

useId是一个新的 hook,用于在客户端和服务器上生成唯一 ID,同时避免 hydration 不匹配。

function CodeOfConductField() {
  const id = useId();
 
  return (
    <>
      <label htmlFor={id}>您是否同意我们的行为准则?</label>
      <input id={id} type="checkbox" name="edemao" />
    </>
  );
}

useTransition

useTransition 是新的 hook,返回转换的挂起状态的有状态值,以及启动它的函数。

const [isPending, startTransition] = useTransition();

startTransition 允许将回调中的更新标记为 transiton;

isPending 指示 transiton 何时处于 active 状态:

useDeferredValue

useDeferredValue 允许推迟重新渲染树的非紧急部分。它类似于去抖动或节流,但有一些优点。 没有固定的时间延迟,因此 React 将在第一次渲染反映在屏幕上后立即尝试延迟渲染。 延迟渲染是可中断的,不会阻止用户输入。比如:

function SearchResults() {
  const query = useSearchQuery("");
  const deferredQuery = useDeferredValue(query);
 
  /** 告诉 React 只有在 deferredQuery 改变时才重新渲染,而不是 query改变时 */
  const suggestionResults = useMemo(
    () => <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );
 
  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="正在加载搜索结果...">
        {suggestionResults}
      </Suspense>
    </>
  );
}

上述例子中,SearchSuggestions 组件将仅在 deferredQuery 更新时重新呈现。 为了将所有内容联系在一起,当 SearchSuggestions suspended 时,我们会看到“正在加载搜索结果...”的文本。 

useSyncExternalStore

useSyncExternalStore 是用于以与选择性水合和时间切片等并发渲染功能兼容的方式从外部数据源读取和订阅的 hook。此 hook 适用于维护第三方库的作者,通常不用于应用程序代码。

useInsertionEffect

useInsertionEffect 的签名与 useEffect 相同,但它在所有 DOM 突变之前同步触发。 这个 hook 用于在 useLayoutEffect 中读取布局之前将样式注入 DOM。 它无权访问 refs,也无法安排更新。useInsertionEffect 仅限于 css-in-js 库作者。

其他

1. 可以渲染 undefined,即如果从组件返回 undefined,React 不再抛出错误。

2. React 现在依赖于现代浏览器功能,包括PromiseSymbolObject.assign.因此,如果要支持旧版浏览器和设备(例如 Internet Explorer),请考虑在捆绑的应用程序中包含一个全局 polyfill。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值