likes
comments
collection
share

React 19 Beta来啦!

作者站长头像
站长
· 阅读数 68

原文:react.dev/blog/2024/0…

作者:React 团队 2024年4月25日

注意:此 Beta 版本发布是为了让库为 React 19 做好准备。应用程序开发者应该升级到 18.3.0 并等待 React 19 稳定版,因为我们将与库合作并根据反馈进行更改。

React 19 Beta 现已在 npm 上提供!

在我们的 React 19 Beta 升级指南 中,我们分享了将您的应用升级到 React 19 Beta 的逐步说明。在这篇文章中,我们将概述 React 19 中的新功能以及您如何采用它们。

React 19 中的新增功能

Actions

在 React 应用中一个常见的用例是执行数据变更,然后相应地更新状态。例如,当用户提交表单以更改他们的姓名时,你将发出 API 请求,然后处理响应。在过去,你需要手动处理待处理状态、错误、乐观更新和顺序请求。

例如,你可以在 useState 中处理待处理和错误状态:

// Before Actions
function UpdateName({}) {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);

  const handleSubmit = async () => {
    setIsPending(true);
    const error = await updateName(name);
    setIsPending(false);
    if (error) {
      setError(error);
      return;
    } 
    redirect("/path");
  };

  return (
    <div>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}

在 React 19 中,我们正在添加对在转换中使用异步函数的支持,以自动处理待处理状态、错误、表单和乐观更新。

例如,你可以使用useTransition处理待处理状态:

// Using pending state from Actions
function UpdateName({}) {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = async () => {
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      } 
      redirect("/path");
    })
  };

  return (
    <div>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}

异步转换将立即将isPending状态设置为true,进行异步请求,并在任何转换后将isPending切换为fals。这使你可以在数据更改时保持当前 UI 的响应性和交互性。

按照惯例,使用异步过渡的功能通常被称为Actions

Actions会自动帮助你提交数据:

  • Pending状态:Actions提供一个从请求开始时就开始的pending状态,并在最终状态更新提交时自动重置。
  • 乐观更新:Actions支持新的useOptimistic hook,因此你可以在请求提交时向用户显示即时反馈。
  • 错误处理:Actions提供错误处理,因此你可以在请求失败时显示错误边界,并自动将乐观更新恢复到其原始值。
  • 表单:<form>元素现在支持将函数传递给actionformAction props。将函数传递给action props会默认使用Actions,并在提交后自动重置表单。

React 19中,基于Actions,引入了useOptimistic来管理乐观更新,还有一个新的hook React.useActionState来处理Actions的常见情况。在react-dom中,我们正在添加<form> Actions来自动管理表单,并且添加useFormStatus来支持表单中Actions的常见情况。

在React 19中,上述示例可以简化为:

// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) {
        return error;
      }
      redirect("/path");
    }
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

在下一节中,我们将详细介绍 React 19 中的每个新 Action 功能。

新钩子:useActionState

为了使操作更容易处理常见情况,我们添加了一个名为useActionState的新钩子:

const [error, submitAction, isPending] = useActionState(async (previousState, newName) => {
  const {error} = await updateName(newName);
  if (!error) {
    // You can return any result of the action.
    // Here, we return only the error.
    return error;
  }

  // handle success
});

useActionState接受一个函数(Action),并返回一个包装后的 Action 以供调用。这在 Actions 组合时非常有效。当调用包装后的 Action 时,useActionState 将返回 Action 的最后结果作为数据,并返回 Action 的挂起状态作为 pending

React.useActionState之前在Canary版本中被称为ReactDOM.useFormState,但我们已将其重命名并弃用useFormState。详细信息请参见#28491

更多信息,请参阅useActionState文档。

React DOM:<form> action

Actions也集成了 React 19 新的 <form>功能,用于 react-dom。我们支持将函数传递为<form><input><button> 元素的 action 和 formAction 属性,以便自动提交包含 Actions 的表单。

<form action={actionFunction}>

<form> Action 成功时,React 会自动重置不受控制组件的表单。如果需要<form>手动重置,可以调用新的requestFormResetReact DOM API。

有关更多信息,请参阅react-dom文档中的<form><input><button>

React DOM:新钩子:useFormStatus

在设计系统中,通常会编写设计组件,这些组件需要访问有关它们所在的<form>的信息,而无需将props传递给组件。这可以通过Context来实现,但为了使常见情况更容易处理,我们添加了一个新的hook useFormStatus

import {useFormStatus} from 'react-dom';

function DesignButton() {
  const {pending} = useFormStatus();
  return <button type="submit" disabled={pending} />
}

useFormStatus读取父级的状态<form>,就像表单是上下文提供者一样。

更多信息,请参阅react-dom的文档 useFormStatus

新钩子:useOptimistic

在执行数据变更时,另一个常见的用户界面模式是在异步请求进行时乐观地显示最终状态。在 React 19 中,我们添加了一个名为useOptimistic的新钩子,以使这更容易:

function ChangeName({currentName, onUpdateName}) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async formData => {
    const newName = formData.get("name");
    setOptimisticName(newName);
    const updatedName = await updateName(newName);
    onUpdateName(updatedName);
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <p>
        <label>Change Name:</label>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName}
          />
      </p>
    </form>
  );
}

使用 useOptimistic 钩子将在 updateName 请求进行时立即呈现 optimisticName。当更新完成或出错时,React 将自动切换回 currentName 值。

更多信息,请参阅 useOptimistic 的文档。

新的API:use

在 React 19 中,我们引入了一个新的 API 来读取渲染中的资源:use

例如,你可以使用use读取promise,React将挂起,直到promise解析:

import {use} from 'react';

function Comments({commentsPromise}) {
  // `use` will suspend until the promise resolves.
  const comments = use(commentsPromise);
  return comments.map(comment => <p key={comment.id}>{comment}</p>);
}

function Page({commentsPromise}) {
  // When `use` suspends in Comments,
  // this Suspense boundary will be shown.
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

use不支持渲染中创建的 Promise

如果你尝试将 render 中创建的 Promise 传递给useReact 会发出警告: React 19 Beta来啦! 要解决此问题,你需要从支持对Promise进行缓存的 Suspense 引擎的库或框架中传递一个Promise。未来,我们计划推出功能,使在 render 中缓存Promise变得更加容易。

你也可以使用use读取上下文,允许你有条件地读取上下文,例如在早期返回之后:

import {use} from 'react';
import ThemeContext from './ThemeContext'

function Heading({children}) {
  if (children == null) {
    return null;
  }

  // This would not work with useContext
  // because of the early return.
  const theme = use(ThemeContext);
  return (
    <h1 style={{color: theme.color}}>
      {children}
    </h1>
  );
}

use API 只能在渲染中调用,类似于钩子。与钩子不同的是,use可以有条件地调用。未来我们计划支持更多使用use在渲染中消费资源的方式。

更多信息,请参阅 use 的文档。

React服务器组件

服务器组件

服务器组件是一个新的选项,它允许在捆绑(bundling)之前、在与你的客户端应用程序或 SSR 服务器分开的环境中提前渲染组件。这个单独的环境就是 React 服务器组件中的“服务器”。服务器组件可以在你的 CI 服务器上的构建时运行一次,也可以使用网络服务器为每个请求运行。

React 19 包含了从Canary通道中包含的所有 React 服务器组件功能。这意味着随服务器组件一起发布的库现在可以将 React 19 作为同等依赖项,并为在支持全栈 React 架构的框架中使用提供 react-server 导出条件

如何建立对服务器组件的支持?

虽然 React 19 中的 React 服务器组件是稳定的,并且在主要版本之间不会中断,但用于实现 React 服务器组件捆绑器或框架的底层 API 不遵循 semver 并且可能在 React 19.x 的小版本之间中断。

要作为捆绑器或框架支持 React 服务器组件,我们建议固定到特定的 React 版本,或使用Canary 版本。我们将继续与捆绑器和框架合作,以在未来稳定用于实现 React 服务器组件的 API。

更多信息,请参阅 React Server Components的文档。

Server Actions

Server Actions允许客户端组件调用在服务器上执行的异步函数。

当使用 "use server" 指令定义了 Server Action 后,你的框架将自动为服务器函数创建引用,并将该引用传递给客户端组件。当客户端调用该函数时,React 将发送请求到服务器执行函数,并返回结果。

没有针对服务器组件的指令

"服务器组件"并不是通过"use server"指令来表示的,实际上并没有针对服务器组件的指令。"use server"指令是用于Server Actions。

更多信息,请参阅 Directives的文档。

服务器操作可以在服务器组件中创建并作为属性传递给客户端组件,也可以在客户端组件中导入和使用。

更多信息,请参阅 React Server Actions文档。

React 19 的改进

ref 作为 prop

从 React 19 开始,你现在可以ref作为函数组件的prop进行访问:

function MyInput({placeholder, ref}) {
  return <input placeholder={placeholder} ref={ref} />
}

//...
<MyInput ref={ref} />

新的函数组件将不再需要 forwardRef,我们将发布一个代码模式来自动更新你的组件以使用新的 ref 属性。在未来版本中,我们将弃用并移除 forwardRef

refs传递给类的参数不会作为 props 传递,因为它们引用了组件实例。

水合错误的差异

我们还改进了在react-dom中关于水合错误的错误报告。例如,与其在开发环境中记录多个错误但没有提供有关不匹配的任何信息: React 19 Beta来啦! 我们现在记录一条单独的消息,显示不匹配的差异: React 19 Beta来啦!

<Context>作为提供者

在 React 19 中,你可以呈现<Context>为提供者,而不是<Context.Provider>

const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}

ContextReact中用于共享状态的关键组件,新的上下文提供者可以使用 <Context>,我们将发布一个代码模式来转换现有提供程序。在将来的版本中,我们将弃用 <Context.Provider>

refs的清理函数

我们现在支持从回调返回清理函数ref

<input
  ref={(ref) => {
    // ref created

    // NEW: return a cleanup function to reset
    // the ref when element is removed from DOM.
    return () => {
      // ref cleanup
    };
  }}
  />

当组件卸载时,React会调用从ref回调返回的清除函数。这适用于DOM引用、对类组件的引用以及useImperativeHandle

React中,过去会在卸载组件时使用null调用ref函数。如果你的ref返回一个清理函数,React现在将跳过这一步。

在未来的版本中,我们将弃用在卸载组件时使用null调用refs的做法。

由于引入了ref cleanup函数,从ref回调中返回其他任何内容现在将被TypeScript拒绝。修复通常是停止使用隐式返回,例如:

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

原始代码返回了HTMLDivElement的实例,而TypeScript无法确定这是否应该是清理功能,或者你是否不希望返回清理功能。

可以使用 no-implicit-ref-callback-return 对这种模式进行代码模式转换。

useDeferredValue初始值

我们为useDeferredValue添加了一个initialValue选项:

function Search({deferredValue}) {
  // On initial render the value is ''.
  // Then a re-render is scheduled with the deferredValue.
  const value = useDeferredValue(deferredValue, '');
  
  return (
    <Results query={value} />
  );
}

当提供初始值时,useDeferredValue 会返回它作为组件初始渲染的值,并使用返回的deferredValue在后台安排重新渲染。

更多信息,请参阅useDeferredValue

对文档元数据的支持

在 HTML 中,像 <title><link><meta> 这样的文档元数据标签是预留放置在文档的 <head> 部分。在 React 中,决定应用程序适当的元数据的组件可能与你渲染 <head> 的地方相距很远,或者 React 并不渲染 <head>

在过去,这些元素需要在effect中手动插入,或者由像 react-helmet 这样的库插入,并且在服务器渲染 React 应用程序时需要仔细处理。

在 React 19 中,我们正在为在组件中原生渲染文档元数据标签添加支持:

function BlogPost({post}) {
  return (
    <article>
      <h1>{post.title}</h1>
      <title>{post.title}</title>
      <meta name="author" content="Josh" />
      <link rel="author" href="https://twitter.com/joshcstory/" />
      <meta name="keywords" content={post.keywords} />
      <p>
        Eee equals em-see-squared...
      </p>
    </article>
  );
}

当 React 渲染这个组件时,它将看到 <title><link><meta>标签,并自动将它们提升到文档的<head>部分。通过本地支持这些元数据标签,我们能够确保它们与仅客户端应用程序、流式 SSR 和服务器组件一起使用。

你可能仍然希望拥有一个元数据库

对于简单用例,将文档元数据呈现为标记可能是合适的,但库可以提供更强大的功能,比如根据当前路由覆盖通用元数据与特定元数据。因此,这些功能使得像 react-helmet这样的框架和库更容易支持元数据标记,而不是替换它们。

详细信息,请参阅<title><link><meta>的文档。

支持样式表

样式表,无论是外部链接的(<link rel="stylesheet" href="...">)还是内联的(<style>...</style>),由于样式优先级规则,都需要在 DOM 中仔细定位。构建一种样式表功能,以允许在组件内进行可组合性是困难的,因此用户通常最终要么将其所有样式加载到可能依赖它们的组件很远的地方,要么使用封装了这种复杂性的样式库。

在 React 19 中,我们正在解决这种复杂性,并通过内置对样式表的支持,为客户端上的并发渲染和服务器上的流式渲染提供更深入的集成。如果你告诉 React 样式表的优先级,它将管理样式表在 DOM 中的插入顺序,并确保(如果是外部的)在显示依赖这些样式规则的内容之前加载样式表。

function ComponentOne() {
  return (
    <Suspense fallback="loading...">
      <link rel="stylesheet" href="foo" precedence="default" />
      <link rel="stylesheet" href="bar" precedence="high" />
      <article class="foo-class bar-class">
        {...}
      </article>
    </Suspense>
  )
}

function ComponentTwo() {
  return (
    <div>
      <p>{...}</p>
      <link rel="stylesheet" href="baz" precedence="default" />  <-- will be inserted between foo & bar
    </div>
  )
}

在服务器端渲染期间,React 将把样式表包含在 <head> 中,这确保浏览器在加载完成之前不会进行绘制。如果在我们已经开始流式传输后很晚才发现样式表,React 将确保在客户端将样式表插入到 <head> 中,然后再显示依赖该样式表的悬念边界的内容。

在客户端渲染期间,React 将等待新渲染的样式表加载完成后再提交渲染。如果你从应用程序中的多个位置渲染此组件,React 将仅在文档中包含一次样式表:

function App() {
  return <>
    <ComponentOne />
    ...
    <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM
  </>
}

对于习惯手动加载样式表的用户来说,这是一个将这些样式表与依赖它们的组件放在一起的机会,以便进行更好的局部推理,并更容易确保只加载你实际依赖的样式表。

样式库和与捆绑器的样式集成也可以采用这种新功能,因此即使你不直接渲染自己的样式表,当你的工具升级以使用此功能时,你仍然可以受益。

有关更多详细信息,请阅读<link><style>的文档。

支持异步脚本

在 HTML 普通脚本(<script src="...">)和延迟脚本(<script defer="" src="...">)按文档顺序加载,这使得在组件树内部深处渲染这些类型的脚本具有挑战性。然而,异步脚本(<script async="" src="...">)将以任意顺序加载。

在 React 19 中,我们通过允许你在组件树的任何位置渲染它们来更好地支持异步脚本,将其放置在实际依赖于该脚本的组件内,而无需管理重新定位和去重脚本实例。

function MyComponent() {
  return (
    <div>
      <script async={true} src="..." />
      Hello World
    </div>
  )
}

function App() {
  <html>
    <body>
      <MyComponent>
      ...
      <MyComponent> // won't lead to duplicate script in the DOM
    </body>
  </html>
}

在所有渲染环境中,异步脚本将被去重,这样 React 就只会加载和执行一次脚本,即使它被多个不同的组件渲染。

在服务器端渲染中,异步脚本将包含在<head> 中,并优先于阻塞绘制的更关键资源,例如样式表、字体和图像预加载。

有关更多详细信息,请阅读<script>的文档。

支持预加载资源

在初始文档加载和客户端更新时,尽早告知浏览器可能需要尽快加载的资源,对页面性能有显著影响。

React 19 包括许多新 API,用于加载和预加载浏览器资源,使构建出色体验变得尽可能简单,不会受制于效率低下的资源加载。

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'

function MyComponent() {
  preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
  preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
  preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
  prefetchDNS('https://...') // when you may not actually request anything from this host
  preconnect('https://...') // when you will request something but aren't sure what
}
<!-- the above would result in the following DOM/HTML -->
<html>
  <head>
    <!-- links/scripts are prioritized by their utility to early loading, not call order -->
    <link rel="prefetch-dns" href="https://...">
    <link rel="preconnect" href="https://...">
    <link rel="preload" as="font" href="https://.../path/to/font.woff">
    <link rel="preload" as="style" href="https://.../path/to/stylesheet.css">
    <script async="" src="https://.../path/to/some/script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

这些 API 可用于通过将对额外资源(如字体)的发现移出样式表加载来优化初始页面加载。它们还可以通过预取预期导航所使用的资源列表,然后在单击甚至悬停时急切地预加载这些资源,从而使客户端更新速度更快。

有关更多详细信息,请参阅资源预加载 API

与第三方脚本和扩展的兼容性

我们改进了水合作用,以兼容第三方脚本和浏览器扩展。

在水合时,如果在客户端上呈现的元素与从服务器中找到的 HTML 中的元素不匹配,React 将强制进行客户端重新渲染以修复内容。在过去,如果第三方脚本或浏览器扩展插入了元素,它会触发不匹配错误和客户端渲染。

在 React 19 中,<head> <body>中的意外标记将被跳过,避免不匹配错误。如果 React 需要因非相关水合不匹配而重新渲染整个文档,它将保留由第三方脚本和浏览器扩展插入的样式表。

更好的错误报告

我们在 React 19 中改进了错误处理,以消除重复并提供处理已捕获和未捕获错误的选项。例如,在呈现时发生错误并被错误边界捕获时,以前 React 会抛出错误两次(一次为原始错误,然后在自动恢复失败后再次抛出),然后调用console.error 并提供关于错误发生位置的信息。

每次捕获到错误都会导致三个错误: React 19 Beta来啦! 在 React 19 中,我们记录一个错误,其中包含所有错误信息: React 19 Beta来啦! 另外,我们还添加了两个新的根选项,以补充onRecoverableError

  • onCaughtError:在 React 在错误边界中捕获错误时调用。
  • onUncaughtError:当错误被抛出且未被错误边界捕获时调用。
  • onRecoverableError:当错误被抛出并自动恢复时调用。

有关更多信息和示例,请参阅 createRoothydrateRoot 的文档。

支持自定义元素

React 19 增加了对自定义元素的全面支持,并通过了在 Custom Elements Everywhere 上的所有测试。

在过去的版本中,在 React 中使用自定义元素一直很困难,因为 React 将无法识别的props视为attributes而不是properties。在 React 19 中,我们添加了对properties的支持,该支持在客户端和 SSR 期间使用以下策略工作:

  • 服务器端渲染:传递给自定义元素的props如果其类型是像stringnumber或值为true的原始值,将呈现为attributes。具有非原始类型(如objectsymbolfunction或值为false)的props将被省略。
  • 客户端渲染:与自定义元素实例上的props匹配的属性将被分配为properties,否则它们将被分配为attributes

感谢 Joey Arhar 推动了 React 中自定义元素支持的设计和实现。

如何升级

请参阅 React 19 升级指南,了解逐步说明和突破性以及显著变更的完整列表。

转载自:https://juejin.cn/post/7361700765764648995
评论
请登录