React19新特性

Actions

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

  • React18之前:

    jsx
    // 没有 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>
      );
    }
  • React18:可以使用 useTransition 来处理待定状态

    jsx
    // 使用 Actions 中的待定状态
    function UpdateName({}) {
      const [name, setName] = useState("");
      const [error, setError] = useState(null);
      const [isPending, startTransition] = useTransition();
    
      const handleSubmit = () => {
        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>
      );
    }
  • React19:在 Action 中自动处理待定状态、错误、表单和乐观更新

    jsx
    // 使用表单的 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}>
          <input type="text" name="name" />
          <button type="submit" disabled={isPending}>Update</button>
          {error && <p>{error}</p>}
        </form>
      );
    }

Actions 其实就是异步过渡函数:

image-20250417120945360

在 Actions 的基础上,React 19 引入了三个不同的 hooks 来处理不同的情况:

乐观更新和非紧急更新对比:

  1. 乐观更新(Optimistic Update)
    • 定义:假设用户操作大概率成功,在异步请求完成前直接更新 UI,若后续请求失败则回滚状态
    • 场景:适用于需要即时反馈的交互,如聊天消息发送、购物车商品增减等
    • API:通过 useOptimistic 钩子实现临时状态副本的更新与回滚
    • 特点:
      • 主动预测:UI 变化不依赖后端响应,直接体现用户操作结果
      • 容错机制:失败时自动恢复原始状态,避免数据不一致
  2. 非紧急更新(Transition)
    • 定义:通过 startTransitionuseTransition 标记的更新,允许 React 将其延迟处理,避免阻塞高优先级交互(如输入框输入)
    • 场景:适用于非直接交互的更新,如下拉筛选、批量数据加载等
    • 特点:
      • 调度优先级:属于 React 内部的任务调度机制,优化渲染性能
      • 无状态回滚:仅控制更新时机,不处理状态回滚逻辑

React 19 在 react-dom 中将 Actions 集成到了新的 <form> 功能中。现在 <form><input><button>actionformAction 属性可以直接接收函数,实现表单的自动提交。

  • 使用方式示例:<form action={actionFunction}>
  • <form> 的 Action 成功后,React 会自动重置非受控组件的表单。
  • 若需要手动重置表单,可使用新的 requestFormReset API。

下面是三个hooks的使用示例:

  • useActionState

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

jsx
import {useFormStatus} from 'react-dom';

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

    • useOptimistic Hook 会在 updateName 请求进行时立即渲染 optimisticName。当更新完成或出错时,React 将自动切换回 currentName 值。
    jsx
    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>
      );
    }

新的 API:use

use 可以读取 promise

  • 传统的方式(不使用 use

    tsx
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    import { useState, useEffect } from 'react'
    import './App.css'
    
    interface DisplayDataProps {
      promise: Promise<string> | null
    }
    
    // 模拟随机成功/失败的请求
    function fetchData(): Promise<string> {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.5
            ? resolve('数据加载成功!')
            : reject(new Error('❌ 网络请求失败'))
        }, 1000)
      })
    }
    
    function DisplayData({ promise }: DisplayDataProps) {
      // ✅ 传统方式:手动管理三个状态
      const [data, setData] = useState<string | null>(null);
      const [isLoading, setIsLoading] = useState(false);
      const [error, setError] = useState<string | null>(null);
    
      // ✅ 用 useEffect 监听 promise 变化
      useEffect(() => {
        if (!promise) return;
    
        let isMounted = true;
    
        setIsLoading(true);
        setError(null);
        setData(null);
    
        promise
          .then(result => {
            if (isMounted) {
              setData(result);
              setIsLoading(false);
            }
          })
          .catch(err => {
            if (isMounted) {
              setError(err.message);
              setIsLoading(false);
            }
          });
    
        return () => {
          isMounted = false;
        };
      }, [promise]);
    
      // ✅ 根据状态渲染不同内容
      if (error) {
        return <p style={{ color: 'red' }}>{error}</p>;
      }
    
      if (isLoading) {
        return <p>⌛ 加载中...</p>;
      }
    
      return <p>{data}</p>;
    }
    
    export default function App() {
      const [promise, setPromise] = useState<Promise<string> | null>(null);
    
      const handleClick = () => {
        setPromise(fetchData());
      };
    
      return (
        <>
          <button onClick={handleClick}>点击加载</button>
          <DisplayData promise={promise} />
        </>
      )
    }
  • 使用 use 读取 promise

    tsx
    /* eslint-disable @typescript-eslint/no-unused-expressions */
    import { use, Suspense, useState } from 'react'
    import './App.css'
    
    interface DisplayDataProps {
      promise: Promise<string | void>
    }
    
    // 模拟随机成功/失败的请求
    function fetchData(): Promise<string> {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.5
            ? resolve('数据加载成功!')
            : reject('❌ 网络请求失败')
        }, 1000)
      })
    }
    
    function DisplayData({ promise }: DisplayDataProps) {
      // ✅ use() 会自动等待 Promise 完成
      const data = use(promise) as string
      return <p>{data}</p>
    }
    
    export default function App() {
      const [promise, setPromise] = useState<Promise<string | void> | null>(null)
      const [error, setError] = useState(null)
    
      const handleClick = () => {
        // 重置状态
        setError(null)
        // ✅ 核心:用 .catch 捕获错误并更新状态
        setPromise(fetchData().catch(err => setError(err)))
      }
    
      return (
        <>
          <button onClick={handleClick}>点击加载</button>
    
          {/* 错误提示 */}
          {error && <p style={{ color: 'red' }}>{error}</p>}
    
          {/* ✅ 仅在无错误时显示 Suspense */}
          {!error && promise && (
            <Suspense fallback={<p>⌛ 加载中...</p>}>
              <DisplayData promise={promise} />
            </Suspense>
          )}
        </>
      )
    }

使用 use 的优化方案:通过 Promise.all 并行请求,减少等待时间

jsx
function Profile({ userId }) {
  const [user, posts] = use(Promise.all([fetchUser(userId), fetchPosts(userId)]));
  return <Dashboard user={user} posts={posts} />;
}

运行时序对比:

  • 传统方案流程

    text
    初次渲染 → 显示加载 → 触发 useEffect
    
    Promise 完成 → 更新状态 → 触发重新渲染
    
    根据新状态显示数据/错误
  • 使用 use 的流程

    text
    渲染组件 → use(promise) → 自动挂起 → Suspense 接管
    
    Promise 完成 → React 自动重新渲染

use 可以条件式读取 context

  • 传统方式(useContext):必须在组件顶层调用,无法在条件分支或循环中使用

    jsx
    function Button({ show }) {
      const theme = useContext(ThemeContext); // 必须始终调用
      if (!show) return null; // 即使不渲染,仍会订阅 Context
    }
  • 使用 use 后:支持按需动态读取 Context,减少不必要的订阅

    jsx
    function Button({ show }) {
      if (show) {
        const theme = use(ThemeContext); // 仅在需要时读取
        return <button style={{ theme }}>Click</button>;
      }
      return null;
    }

ref 的改进

可以在函数组件中将 ref 作为 prop 进行访问:新的函数组件将不再需要 forwardRef

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

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

refs 支持清理函数:当组件卸载时,React 将调用从 ref 回调返回的清理函数。

例如,你可以在 ref 改变时取消订阅事件:

jsx
<input
  ref={(ref) => {
    // ref 创建

    // 新特性: 当元素从 DOM 中被移除时
    // 返回一个清理函数来重置 ref
    return () => {
      // ref cleanup
    };
  }}
/>

useDeferredValue 初始化 value

useDeferredValue 添加了一个 initialValue 选项:

jsx
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} />
  );
}

当提供了 initialValue 时, useDeferredValue 将在组件的初始渲染中返回它作为 value,重新渲染时返回 deferredValue

<Context> 的使用方式

在 React 19 中,可以将 <Context> 渲染为提供者,无需再使用 <Context.Provider> 了:

jsx
const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}
还有一些服务器端的更新和错误提示的修改:https://zh-hans.react.dev/blog/2024/12/05/react-19
album
profileHersan

热爱编程,开源社区活跃参与者

7文章
0标签
10分类