My Personal Blog

Published on

React v19 的新特性概览

Authors
  • avatar
    Name
    Tian Haipeng

服务器组件(RSC):经过多年的开发,React 引入了服务器组件,而不是需要借助Next.js

动作(Action):Action也将彻底改变我们与 DOM 元素的交互方式。

增强的 hooks:引入了很多新 hooks,将改变我们的编码体验。

1.如何使用服务器组件

默认情况下,React 中的所有组件都是客户端组件。只有使用 'use server'` 时,组件才是服务器组件。

我们只需要将 'use server' 添加为组件的第一行即可。这将使组件成为服务器组件。它不会在客户端运行,只会在服务器端运行。

'use server'

export default async function requestUsername(formData) {
  const username = formData.get('username')
  if (canRequest(username)) {
    // ...
    return 'successful'
  }
  return 'failed'
}

2.动作(Action

什么是 Action

使用异步转换的函数被称为Action(动作)。Action自动管理数据的提交:

  1. Pending 状态:Action 提供了一个 state
    • 请求开始时,代表对应的状态 - pending状态
    • 请求结束时,状态自动重置
  2. Optimistic更新:Action支持新的useOptimistic hook,因此我们可以在请求提交时向用户显示即时反馈。
  3. 错误处理:Action提供错误处理,因此我们可以在请求失败时显示Error Boundary,并自动恢复Optimistic更新为其原始值。
  4. 增强表单操作:<form>元素支持将函数传递给 action 和 formAction props。
    • 传递给action props的函数默认使用Action机制,并在提交后自动重置表单

Action将允许我们将action<form/>标签 集成。简单来说,我们将能够用action替换 onSubmit 事件。

在使用 Action 之前

在下面的代码片段中,利用 onSubmit事件,在表单提交时触发搜索操作。

<form onSubmit={search}>
  <input name="query" />
  <button type="submit">查询</button>
</form>

使用 Action 之后

随着服务器组件的引入, Action可以在服务器端执行。在 JSX 中,我们可以删除 <form/>onSubmit 事件,并使用 action 属性。action 属性的值将是一个提交数据的方法,可以在客户端服务器端提交数据。

我们可以使用Action执行同步异步操作,简化数据提交管理和状态更新。为的是使处理表单和数据更加容易。

'use server'

const submitData = async (userData) => {
  const newUser = {
    username: userData.get('username'),
    email: userData.get('email'),
  }
  console.log(newUser)
}
const Form = () => {
  return (
    <form action={submitData}>
      <div>
        <label>用户名</label>
        <input type="text" name="username" />
      </div>
      <div>
        <label>邮箱</label>
        <input type="text" name="email" />
      </div>
      <button type="submit">提交</button>
    </form>
  )
}

export default Form

在上面的代码中,submitData 是服务器组件中的Actionform 是一个客户端组件,它使用 submitData 作为ActionsubmitData 将在服务器上执行。

3.Hooks

React 19 中,我们使用 useMemoforwardRefuseEffectuseContext 的方式将会改变。这主要是因为将引入一个新的 hook,即 use

useMemo()

之前的写法

import React, { useState, useMemo } from 'react'

function ExampleComponent() {
  const [inputValue, setInputValue] = useState('')

  // 记住输入框是否为空的检查结果
  const isInputEmpty = useMemo(() => {
    console.log('检测输入框是否为空')
    return inputValue.trim() === ''
  }, [inputValue])

  return (
    <div>
      <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
      <p>{isInputEmpty ? 'Input 为空' : 'Input有值'}</p>
    </div>
  )
}

export default ExampleComponent

之后的写法

import React, { useState } from 'react';

function ExampleComponent() {
  const [inputValue, setInputValue] = useState('');

  const isInputEmpty = () => {
    console.log('检测输入框是否为空');
    return inputValue.trim() === '';
  });

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <p>{isInputEmpty ? 'Input 为空' : 'Input有值'}</p>
    </div>
  );
}

export default ExampleComponent;

我们可以看到在 React19 之后,我们不再需要自己来做记忆化,React19 将会在后台自动完成。

forwardRef()

ref 现在将作为props传递而不是使用 forwardRef() hook。

这将简化代码。因此,在 React19 之后,我们不需要使用 forwardRef()

之前的写法

import React, { forwardRef } from 'react'

const ExampleButton = forwardRef((props, ref) => <button ref={ref}>{props.children}</button>)

之后的写法

ref 可以作为属性传递。不再需要 forwardRef()

import React from 'react'

const ExampleButton = ({ ref, children }) => <button ref={ref}>{children}</button>

新的 use() hook

这个 hook 将简化我们如何使用 promisesasync 代码和 context

const value = use(resource)

示例 1:接收 async 函数

下面的代码是使用 use hook 进行 fetch 请求的示例:

import { use } from 'react'

const fetchUsers = async () => {
  const res = await fetch('远程地址')
  return res.json()
}

const UsersItems = () => {
  const users = use(fetchUsers())

  return (
    <ul>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
          <p>{user.email}</p>
        </div>
      ))}
    </ul>
  )
}
export default UsersItems
  • fetchUsers进行远程数据请求
  • 我们使用 use hook 执行 fetchUsers,而不是使用 useEffectuseState hooks。
  • use hook 的返回值是 users,其中包含 GET 请求的响应(users)。
  • return中,我们使用 users进行对应信息的渲染处理。

示例 2:接收 context 对象

我们以后可以直接将context对象传人到use()中,从而达到将context引入组件的目的。而不需要useContext()了。

使用 createContext 定义全局变量

这里我们定义

import { createContext, useState, use } from 'react'

const ThemeContext = createContext()

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light')

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'))
  }

  return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>
}

在组件中使用 use()获取 context 信息

const Card = () => {
  // use Hook()
  const { theme, toggleTheme } = use(ThemeContext);

  return (
    // 基于theme/toggleTheme 渲染页面或者执行对应的操作
  );
};

const Theme = () => {
  return (
    <ThemeProvider>
      <Card />
    </ThemeProvider>
  );
};

export default Theme

上面代码中有几点需要简单解释一下:

  • ThemeProvider 负责提供 context
  • Card 是我们将消费 context 的组件。为此,我们将使用新的 hook use 来消费 context

useFormStatus() hook

React19 中,我们还有新的 hooks 来处理表单状态数据。这将使处理表单更加流畅和简单。将这些 hooksAction结合使用将使处理表单和数据更加容易。

React19 中的这个新 hook 将帮助我们更好地控制你创建的表单。它将提供关于上次表单提交的状态信息。

基础语法

这是它的语法:

const { pending, data, method, action } = useFormStatus()

或者简化的版本:

const { status } = useFormStatus()
  • pending:如果表单处于待处理状态,则为 true,否则为 false
  • data:一个实现了 FormData 接口的对象,其中包含父 <form> 提交的数据。
  • method:HTTP 方法 – GET,或 POST。
    • 默认情况下将是 GET。
  • action:一个函数引用。

案例展示

useFormStatus是从react-dom库中导出的

import { useFormStatus } from 'react-dom'

function Submit() {
  const status = useFormStatus()
  return <button disabled={status.pending}>{status.pending ? '正在提交...' : '提交完成'}</button>
}

// ==== 父组件 ==引入Submit ====

const formAction = async () => {
  // 模拟延迟 3 秒
  await new Promise((resolve) => setTimeout(resolve, 3000))
}

const FormStatus = () => {
  return (
    <form action={formAction}>
      <Submit />
    </form>
  )
}

export default FormStatus

解释一下:

  • Submit通过useFormStatus可以获取此时from表单的提交状态,并基于一些状态渲染一些辅助信息
  • formAction是执行异步提交的处理

在上面的代码中,当表单提交时,从 useFormStatus hook 我们将获得一个 pending 状态。

  • pendingtrue 时,UI 上会显示 "正在提交..." 文本。
  • 一旦 pendingfalse,"正在提交..." 文本将被更改为 "提交完成"。

当我们想要知道表单提交的状态并相应地显示数据时,它会很有用。

useFormState() hook

React19 中的另一个新 hook 是 useFormState。它允许我们根据表单提交的结果来更新状态。

语法

这是它的语法:

const [state, formAction] =
      useFormState(
        fn,
        initialState,
        permalink?
      );
  • fn:表单提交或按钮按下时要调用的函数。
  • initialState:我们希望状态初始值是什么。它可以是任何可序列化的值。在首次调用操作后,此参数将被忽略。
  • permalink:这是可选的。一个 URL 或页面链接,如果 fn 将在服务器上运行,则页面将重定向到 permalink

这个 hook 将返回:

  • state:初始状态将是我们传递给 initialState 的值。
  • formAction:一个将传递给表单操作的操作。此操作的返回值将在状态中可用。

案例展示

import { useFormState } from 'react-dom'

const FormState = () => {
  const submitForm = (prevState, queryData) => {
    const name = queryData.get('username')
    console.log(prevState) // 上一次的from 的state
    if (name === '柒八九') {
      return {
        success: true,
        text: '前端开发者',
      }
    } else {
      return {
        success: false,
        text: 'Error',
      }
    }
  }
  const [message, formAction] = useFormState(submitForm, null)
  return (
    <form action={formAction}>
      <label>用户名</label>
      <input type="text" name="username" />
      <button>提交</button>
      {message && <h1>{message.text}</h1>}
    </form>
  )
}

export default FormState

解释一下

  • submitForm 是负责表单提交的方法。这是一个 Action
  • 在 submitForm 中,我们正在检查表单的值。
    • prevState:初始状态将为 null,之后它将返回表单的 prevState
    • queryData:用于获取此次操作中from表单中对应key的值

useOptimistic() hook

useOptimistic 也新发布的Hook,它允许我们在异步操作时显示不同的状态。

这个 hook 将帮助增强用户体验,并应该导致更快的响应。这对于需要与服务器交互的应用程序非常有用。

语法

以下是 useOptimistic hook 的语法:

const [optimisticX, addOptimisticX] = useOptimistic(state, updatefn)

例如,当响应正在返回时,我们可以显示一个optimistic 状态,以便让用户获得即时响应。一旦服务器返回实际响应,optimistic状态将被替换。