终于等到你,React 19 Beta 发布!

发布于:2024-04-26 ⋅ 阅读:(18) ⋅ 点赞:(0)

2024 年 4 月 25 日,作者:


笔记

此测试版用于为 React 19 做准备的库。应用程序开发人员应升级到 18.3.0 并等待 React 19 稳定版本,因为我们将使用库并根据反馈进行更改。

React 19 Beta 现已在 npm 上可用!

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

有关重大变更的列表,请参阅。


React 19 中的新功能

操作

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在任何转换后切换为 false。这可让您在数据发生变化时保持当前 UI 的响应和交互。

笔记

按照惯例,使用异步转换的功能称为“动作”。

操作会自动为您管理提交数据:

  • 待处理状态:操作提供待处理状态,该状态从请求开始时开始,并在提交最终状态更新时自动重置。
  • 乐观更新:操作支持新的钩子,因此您可以在提交请求时向用户显示即时反馈。
  • 错误处理:操作提供错误处理,以便您可以在请求失败时显示错误边界,并自动将乐观更新恢复为其原始值。
  • 表单<form>元素现在支持将函数传递给actionformAction道具。将函数传递给action道具默认使用 Actions,并在提交后自动重置表单。

在 Actions 的基础上,React 19 引入了管理乐观更新的功能,以及一个处理 Actions 常见情况的新钩子。react-dom我们添加了

来自动管理表单并支持表单中 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 的最后结果data,以 的形式返回 Action 的待处理状态pending

笔记

React.useActionState``ReactDOM.useFormState之前在 Canary 版本中被称为,但是我们已将其重命名并弃用useFormState

请参阅以了解更多信息。

有关详细信息,请参阅文档。

React DOM:<form>操作

<form>Actions 还与 React 19 的新功能集成react-dom。我们增加了将函数作为、和元素的action和属性传递的支持,以便使用 Actions 自动提交表单:formAction``<form>``<input>``<button>

<form action={actionFunction}>

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

有关更多信息,请参阅、和的react-dom文档。

React DOM:新钩子:useFormStatus

在设计系统中,编写需要访问其所<form>在信息的设计组件很常见,而无需深入研究组件的 props。这可以通过 Context 完成,但为了使常见情况更容易,我们添加了一个新的钩子useFormStatus

import {useFormStatus} from 'react-dom';



function DesignButton() {

  const {pending} = useFormStatus();

  return <button type="submit" disabled={pending} />

}

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

有关详细信息,请参阅react-dom文档。

新钩子:useOptimistic

执行数据变异时的另一个常见 UI 模式是在异步请求进行时乐观地显示最终状态。在 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立即渲染。当更新完成或出错时,React 将自动切换回该值。optimisticName``updateName``currentName

有关详细信息,请参阅文档。

新 API:use

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

例如,你可以用 读取一个承诺use,React 将暂停直到承诺解决:

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不支持在渲染中创建的承诺。

如果你尝试将在 render 中创建的 promise 传递给use,React 会发出警告:

image.png

要解决此问题,您需要从支持缓存承诺的 Suspense 库或框架传递承诺。未来我们计划推出一些功能,让在渲染中缓存承诺变得更加容易。

您还可以使用 读取上下文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>

  );

}

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

有关详细信息,请参阅文档。

React 服务器组件

服务器组件

服务器组件是一个新选项,允许在捆绑之前在独立于客户端应用程序或 SSR 服务器的环境中提前渲染组件。这个独立的环境就是 React 服务器组件中的“服务器”。服务器组件可以在 CI 服务器上构建时运行一次,也可以使用 Web 服务器针对每个请求运行。

React 19 包含 Canary 渠道中包含的所有 React Server Components 功能。这意味着 Server Components 附带的库现在可以将 React 19 作为对等依赖项,并带有react-server,以便在支持的框架中使用。

笔记

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

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

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

的文档。

服务器操作

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

当使用该指令定义服务器操作时"use server",您的框架将自动创建对服务器函数的引用,并将该引用传递给客户端组件。当在客户端调用该函数时,React 将向服务器发送请求以执行该函数,并返回结果。

笔记

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

一个常见的误解是服务器组件用 表示"use server",但没有针对服务器组件的指令。"use server"指令用于服务器操作。

文档。

服务器操作可以在服务器组件中创建并作为道具传递给客户端组件,或者可以在客户端组件中导入和使用它们。

的文档。

React 19 中的改进

ref作为道具

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

function MyInput({placeholder, ref}) {

  return <input placeholder={placeholder} ref={ref} />

}



//...

<MyInput ref={ref} />

新的函数组件将不再需要forwardRef,我们将发布一个 codemod 来自动更新您的组件以使用新的refprop。在未来的版本中,我们将弃用并删除forwardRef

笔记

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

水合误差的差异

我们还改进了 中的水合错误的错误报告react-dom。例如,不再在 DEV 中记录多个错误而没有任何有关不匹配的信息:

image.png

我们现在记录一条包含不匹配差异的消息:

image.png

<Context>作为提供者

在 React 19 中,你可以<Context>作为提供程序进行渲染,而不是<Context.Provider>

const ThemeContext = createContext('');



function App({children}) {

  return (

    <ThemeContext value="dark">

      {children}

    </ThemeContext>

  );  

}

新的 Context 提供程序可以使用<Context>,我们将发布 codemod 来转换现有提供程序。在未来的版本中,我们将弃用<Context.Provider>

ref 的清理函数

我们现在支持从回调返回清理函数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 会在卸载组件时调用带有ref的函数。如果您返回清理函数,React 现在将跳过此步骤。null``ref

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

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

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

+ <div ref={current => {instance = current}} />

原始代码返回了实例HTMLDivElement,而 TypeScript 不知道这是否应该是一个清理函数,或者您是否不想返回清理函数。

您可以使用 对此模式进行编码修改。

useDeferredValue初始值

我们添加了一个initialValue选项useDeferredValue

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} />

  );

}

当提供initialValueuseDeferredValue时,将像组件的初始渲染一样返回它,并使用返回的deferredValuevalue在后台安排重新渲染。

更多信息请参见。

支持文档元数据

在 HTML 中,文档元数据标签(如<title><link>和 )<meta>保留用于放置在<head>文档的部分中。在 React 中,决定哪些元数据适合应用程序的组件可能距离您渲染 的位置非常远,<head>或者 React<head>根本不渲染 。过去,这些元素需要手动插入到效果中,或者通过 等库插入,并且在服务器渲染 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 和服务器组件。

笔记

您可能仍需要一个元数据库

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

和的文档。

支持样式表

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

在 React 19 中,我们解决了这一复杂性,并通过内置对样式表的支持,进一步集成了客户端上的并发渲染和服务器上的流式渲染。如果您告诉 Reactprecedence您的样式表,它将管理样式表在 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>在显示依赖于该样式表的 Suspense 边界的内容之前,将样式表插入到客户端的 中。

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

function App() {

  return <>

    <ComponentOne />

    ...

    <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM

  </>

}

对于习惯于手动加载样式表的用户来说,这是一个机会来将这些样式表与依赖它们的组件一起定位,从而可以进行更好的本地推理,并且更容易确保只加载您实际依赖的样式表。

样式库和与捆绑器的样式集成也可以采用这一新功能,因此即使您不直接呈现自己的样式表,您仍然可以受益,因为您的工具已升级以使用此功能。

有关更多详细信息,请阅读和的文档

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

我们改进了水合功能以适应第三方脚本和浏览器扩展。

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

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

更好的错误报告

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

这导致捕获的每个错误都会出现三个错误:

image.png

在 React 19 中,我们记录一个包含所有错误信息的单个错误:

image.png

此外,我们还添加了两个新的根选项来补充onRecoverableError

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

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

支持自定义元素

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

在过去的版本中,在 React 中使用自定义元素很困难,因为 React 将无法识别的 props 视为属性 (attribute) 而不是属性 (property)。在 React 19 中,我们通过以下策略添加了对在客户端和 SSR 期间起作用的属性的支持:

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

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

如何升级

请参阅,获取分步说明以及重大和显著变化的完整列表。

来自: