🔥 React 19 终于发布,一大波新功能正式升级

5,827 阅读6分钟

00-react19.png

作者:React 团队

译者:林语冰

资源:React 官方博客[1]

免责声明:活人翻译,略有删改,仅供粉丝参考!

00. Hello World

大家好,我是大家的 林语冰

React 18 发布两年半后,React 团队终于官宣,React 19 稳定版正式升级。

01. React 19 新功能

1.1 Action

React 应用的常见用例之一是执行数据突变,然后更新状态作为响应。

比如,用户提交表单改名时,你会发出请求,然后处理响应。过去,你需要手动处理挂起状态、错误、乐观更新和串行请求。

举个栗子,以前可以在 useState 中处理挂起和错误状态:

// 旧版的写法:
function UpdateName({}) {
  // const ...
  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 (...)
}

React 19 支持在转换中使用异步函数,自动处理挂起状态、错误、表单和乐观更新。

现在,可以使用 useTransition 来处理挂起状态:

// 新版的写法:
// 使用 Action 的挂起状态
function UpdateName({}) {
  // const ...
  const [isPending, startTransition] = useTransition()

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

异步转换会立即将 isPending 的状态设置为 true,发出异步请求,并在任何转换后将 isPending 切换为 false

按照惯例,使用异步转换的函数称为“Actions”(动作)。

Actions 会自动管理提交的数据:

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

React 19 构建于 Actions 之上,引入 useOptimistic 管理乐观更新,并新增了 React.useActionState hook 来处理 Actions 的常见情况。

react-dom 添加了 <form> Actions 自动管理表单,以及 useFormStatus 来支持表单动作的常见情况。

React 19 可以简化上述例子:

// 使用 <form> Actions 和 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 null
    },
    null
  )

  return <form action={submitAction}>// ...</form>
}

1.2 新 hook:useActionState

我们新增了 useActionState hook:

const [error, submitAction, isPending] = useActionState(
  async (previousState, newName) => {
    const error = await updateName(newName)
    if (error) {
      // 你可以返回 action 的任意结果
      // 这里我们只返回错误
      return error
    }
    // 处理成功情况
    return null
  },
  null
)

useActionState 接受一个 Action 函数,并返回一个包装 Action 来调用。

当调用包装 Action 时,useActionState 会返回 Action 的结果作为 data,并将其挂起状态返回为 pending

1.3 <form> Actions

React 19 还把 Actions 与 react-dom<form> 新功能集成,支持传递函数作为 <form><input><button> 元素的 actionformAction props,自动提交带有 Actions 的表单:

<form action={actionFunction}>

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

1.4 新 hook:useFormStatus

在设计系统中,通常会编写需要访问其所在 <form> 信息的设计组件,而无需将 props 逐层传递到组件。

这可以通过 Context 来完成,但我们新增了一个简化 hook useFormStatus

import { useFormStatus } from 'react-dom'

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

useFormStatus 读取父级 <form> 的状态,就好像该表单是 Context provider 一样。

1.5 新 hook:useOptimistic

执行数据突变的另一种常见 UI 模式是在异步请求正在进行时乐观显示最终状态。

React 19 新增了一个 useOptimistic 简化 hook:

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>{optimisticName}</p>
      <input
        name="name"
        disabled={currentName !== optimisticName}
      />
    </form>
  )
}

updateName 请求正在进行时,useOptimistic hook 会立即渲染 optimisticName。当更新完成或出错时,React 会自动切换回 currentName 的值。

1.6 新 API:use

React 19 新增 use API 来读取渲染中的资源。

举个栗子,你可以使用 use 读取 promise 对象,React 会挂起直到该 promise 解析:

import { use } from 'react'

function Comments({ commentsPromise }) {
  // use 会挂起直到 promise 解析
  const comments = use(commentsPromise)
  return comments.map((comment) => <p key={comment.id}>{comment}</p>)
}

function Page({ commentsPromise }) {
  // 当 use 在 Comments 中挂起时,
  // 这个 Suspense 边界会被显示。
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

use 不支持渲染中创建的 promise 对象。如果你将渲染中创建的 promise 传递给 use,React 会报错。

你还可以使用 use 条件式读取 context,比如在提前 return 之后:

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

function Heading({ children }) {
  if (children == null) {
    return null
  }
  // 由于提前 return,
  // 以前使用 useContext 这行不通。
  const theme = use(ThemeContext)
  return <h1 style={{ color: theme.color }}>{children}</h1>
}

类似于 hooks,use API 只能在渲染中调用。与 hooks 不同的是,use 可以条件调用。

02. 新的 React DOM 静态 API

react-dom/static 新增了两个 API 来生成静态站点:

  • prerender
  • prerenderToNodeStream

这些新 API 通过等待数据加载以生成静态 HTML 来改进 renderToString,为了与 Node Streams 和 Web Streams 等流环境配合使用。

举个栗子,在 Web Stream 环境中,你可以使用 prerender 将 React 树预渲染为静态 HTML:

import { prerender } from 'react-dom/static'

async function handler(request) {
  const { prelude } = await prerender(<App />, {
    bootstrapScripts: ['/main.js'],
  })
  return new Response(prelude, {
    headers: { 'content-type': 'text/html' },
  })
}

prerender API 会等待所有数据加载,然后再返回静态 HTML 流。

流可以转换为字符串,或者与流响应一起发送。它们在加载时不支持流内容,而现有的 React DOM 服务端渲染 API 支持流内容。

03. React 服务器组件

3.1 RSC(服务器组件)

rsc.png

RSC[2] 是一个新选项,允许在打包之前在与客户端应用或 SSR 服务器分开的环境中提前渲染组件。这个单独的环境是 RSC 中的“服务器”。

RSC 可以在构建时在 CI 服务器上运行一次,也可以使用 Web 服务器针对每个请求运行。

3.2 RSA(服务器动作)

rsa.jpg

RSA[3] 允许客户端组件调用在服务器上执行的异步函数。

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

一个常见的误解是使用 "use server" 来表示 RSC,但其实没有针对 RSC 的指令。"use server" 指令只适用于 RSA。

RSA 可以在 RSC 中创建,并作为 props 传递给客户端组件,也可以在客户端组件中导入和使用。

高潮总结

React 19 是 React 18 时隔两年后发布的主版本升级,涉及的新功能较多,RSC 等部分技术细节需要大家进一步阅读文档。

这篇官方博客主要分享了 React 19 的新功能,另外还有针对旧版功能的优化和改良,将在下一篇继续分享。

我是大家的 林语冰 👨‍💻,欢迎持续 关注,随时了解海内外前端开发的最新情报。

谢谢的大家点赞、留言和友情转发,我们下期再见~👍

参考文献

[1] React 官方博客: react.dev/blog/2024/1…

[2] RSC: react.dev/reference/r…

[3] RSA: react.dev/reference/r…