React 19 来了!看看 React 19 带来了什么?逐点解析官方博客 ~

React 19 新增内容

Actions 异步状态处理

useTransition 优化

支持异步函数

新增 hook useActionState

该 hook 原来由 ReactDOM 包导出,由于一些原因,因此做出一些改动 useActionState 的类型如下:

typescript 复制代码
type Type = function useActionState<State>(
    action: (state: Awaited<State>) => State | Promise<State>,
    initialState: Awaited<State>,
    permalink?: string,
): [state: Awaited<State>, dispatch: () => void, boolean];

// 返回一个元组,[state, dispatch, pending]
// state: 最新的状态
// dispatch: 调用 action 的方法
// pending: action/状态更新进度 的状态

使用示例:

javascript 复制代码
import { useActionState } from "react";

function Form({ formAction }) {
  const [state, action, isPending] = useActionState(formAction);

  return (
    <form action={action}>
      <input type="email" name="email" disabled={isPending} />
      <button type="submit" disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </form>
  );
}
javascript 复制代码
// 为什么要更名的原因,一个解释的例子如下
// 因为原来 useFormState 不是一定要和 <form /> 一起使用,因此会有一些混乱

import { useActionState, useRef } from "react";

function Form({ someAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(someAction);

  async function handleSubmit() {
    // See caveats below
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

如果对于 CSR,其实这个 hook 并不是很有优势,这个 hook 主要还是给支持 RSC 的框架使用。 优点:

  • 内部数据变化是通过 useTransition 实现的,因此在结果返回之前保持页面的响应。
  • 自动跟踪包装函数的返回值和 pending 状态,无需自己去维护。
  • 在 RSC 上使用的优点如下:
    • 支持 replay,在水合之前如果触发了表单的 submit,该 hook 会自动重放该 submit,以保证正常的 JavaScript 的执行。

举个栗子:假设你正在填写一个网页上的表单,但你的网络连接不是很好,JavaScript 代码还在加载中。在传统的网页上,如果你在这个时刻提交表单,可能什么都不会发生,因为处理表单提交的 JavaScript 代码还没加载完成。但在使用了 React 19 的
元素和相关钩子的情况下,即使你在 JavaScript 完全加载好之前提交了表单,React 也能确保你的提交被正确处理,一旦 JavaScript 准备好了,它会自动"回放"你的提交操作。 这就像是你试图在电视信号不好的时候换台,虽然一开始画面可能会卡住,但是一旦信号稳定了,电视会自动帮你切换到你想要观看的频道。

与最新的 <form> 集成

<form /> <input /> <button /> 新增 actionformAction 参数

新增 hook useFormStatus

对于很深层次的 form 组件,逐层传递 props 显然是不合适的,通过 context 或者轻量状态管理库是可以解决的,新增的这个 hook 相当于封装了 context。

javascript 复制代码
import {useFormStatus} from 'react-dom';

function DesignButton() {
  // useFormStatus 能够读取上层 form 的状态
  const status = useFormStatus();
  return <button type="submit" disabled={status.pending} />
}

注意点:

  • 该 hook 只能获取父级的 form 的相关数据
  • 返回的数据的类型:
typescript 复制代码
interface Status {
  pending: boolean;
  data: FormData;
  method: 'get' | 'post';
  // 对父级form的action的引用
  action: (state: Awaited<State>) => State | Promise<State>;
}

新增 hook useOptimistic

简单来说就是假设状态更新会成功,如果失败或者执行完成,然后更新 state。主要用在异步操作上,去提高用户的体验。

举个栗子:你在玩一个游戏,点击了一个按钮来升级你的装备。大多数人会期望立即看到装备升级的效果,即使服务器还需要一些时间来处理你的请求。useOptimistic 钩子就是让这种体验成为可能:你点击升级,装备立即显示为升级状态,如果一切顺利,你就保持这个状态;如果有问题,游戏会自动纠正并告诉你出了什么错。这种方式可以减少用户的等待焦虑,让他们感觉操作更加即时和流畅。

新增 API use

  • 与 Promise 结合
javascript 复制代码
import {use} from 'react';

function Comments({commentsPromise}) {
  // use 将等待直到 Promise 由 pending 变为 fullfilled/rejected
  const comments = use(commentsPromise);
  return comments.map(comment => <p key={comment.id}>{comment}</p>);
}

function Page({commentsPromise}) {
  // Suspense 将会触发,展示 Loading ...
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}
  • 与 Context 结合
javascript 复制代码
import {use} from 'react';
import ThemeContext from './ThemeContext'

function Heading({children}) {
  if (children == null) {
    return null;
  }
  
  const theme = use(ThemeContext);
  return (
    <h1 style={{color: theme.color}}>
      {children}
    </h1>
  );
}

通过上面这个例子,我们可以发现 use是可以在条件语句之后执行的,这和其他 hooks 是完全不一样的。

服务器组件 RSC

文档:react.dev/reference/r...

Server Actions

允许客户端组件调用服务器上执行的异步函数。通过指令来表示代码是在客户端运行 or 客户端可以调用的服务器的函数


React 19 优化内容

ref 的优化

这个爽死真的,官方写了一个 codemod,所以不需要再用 forwardRef去获取传入的 ref。 注意:不能在类组件上使用。

jsx 复制代码
function MyInput({ref}) {
  return <input ref={ref} />
}

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

水合时出错信息的优化

将 多个错误信息但是没有用的 合并为 一条并且告知不匹配原因 的信息。

Context 的优化

也是写法的简化,就是在 create 一个 context 的时候不需要在顶层写 xxx.provider了,直接写 xxx即可。

jsx 复制代码
const ThemeContext = createContext('');

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

refs 的优化

之前的做法:

当需要设置 ref 时,React 会调用这个 ref 回调,并传入对应的 DOM 节点作为参数。当需要清除 ref 时,React 会再次调用这个 ref 回调,并传入 null 作为参数。通过使用 ref 回调,就可以在 React 组件的生命周期中执行特定的操作,比如操作 DOM、获取 DOM 属性等。

jsx 复制代码
import { useRef } from 'react';

function ExampleComponent() {
  const myRef = useRef(null);

  function handleRef(node) {
    // 在创建ref/卸载DOM时调用
    if (node) {
      // 执行你的操作,比如操作 DOM
      console.log(node);
    } else {
      // 在清除 ref 时调用,此时 node 是 null
      console.log('Ref is cleared');
    }
  }

  return (
    <div ref={handleRef}>
      {/* ... */}
    </div>
  );
}

优化后:

可以像 useEffect那样返回一个清理函数

jsx 复制代码
<input
  ref={(node) => {
    // ref 创建的时候执行该函数

    // 新增清理函数,在清理 ref 的时候调用该函数
    return () => {
      // 清理 ref
    };
  }}
/>

也就是在创建和卸载的时候调用不同的函数

useDeferredValue

新增第二个参数initialValue,组件初次渲染时该值将作为初始值展示,返回的deferredValue在会在后台进行重新渲染。 如果不传入初始值,useDeferredValue在初次渲染时将不会延迟,因为它没有可以渲染的 value 的先前数据。这个情况的话就和优化之前是一样的了。

支持 HTML Metadata 标签

这个功能推出之后就不用自己去编写方法 or 使用第三方库来根据组件更新 metadata 了(仅对于简单的替换)。

jsx 复制代码
function BlogPost({post}) {
  return (
    <article>
      <h1>{post.title}</h1>
      {/* 这些将会自动提升到 <head> 部分 */}
      <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>
  );
}

stylesheet 样式表

  • 通过在组件内写入 link 标签加载样式表,可以实现将样式表和依赖它们的组件捆绑,更容易管理样式表的加载。
  • 内置支持管理样式表加载顺序的优先级 (precedence)。
  • 在 SSR 中表现的优化
  • CSR:如果加载的多个组件使用同一个样式表,那么只加载一次

script 脚本

<script> 有两种异步加载方式,deferasyncdefer要按照顺序执行,如果组件层级很深,按照顺序去渲染对应的 script 会比较困难,async是任意顺序的去执行。 React 19 在支持组件内写 script 的同时,支持在依赖对应的 script 的组件内加载渲染,不需要手动去管理这些 script。并且它和样式表一样也有去重的功能,多个相同的 script 只会加载并执行一次。

资源预加载

react-dom新增了多个(预)加载的 API

  • preinit: 立即加载并执行期望的脚本
  • preload: 预加载期望使用的资源,比如样式表、字体或外部脚本
  • prefetchDNS: 允许提前查找期望从中加载资源的服务器的 IP
  • preconnect: 帮助提前连接到一个期望从中加载资源的服务器
  • preinitModule: 预获取和评估 ESM 模块
  • preloadModule: 立即预获取期望使用的 ESM 模块

以上 API 将会转换到 html 中 head 对应的标签内

水合时兼容第三方脚本和扩展

  • 忽略未预期的标签:在水合过程中,React 19会忽略 和 中的未预期标签,避免因为第三方脚本或浏览器扩展插入的内容而导致的 mismatch 错误。
  • 保留样式表:即使因为 hydration mismatch 而需要重新渲染整个文档,React 19 也会保留由第三方脚本和浏览器扩展插入的样式表。
  • 更好的错误处理:React 19 改进了错误处理机制,提供了更多的错误处理选项,帮助开发者更好地处理第三方脚本和浏览器扩展可能导致的问题。

对自定义元素(Custom Elements)的支持

在 React 19 之前,React 应用中使用自定义元素可能会遇到的问题:

  • React 会将不认识的属性(props)当作 HTML 属性处理,而不是 DOM 元素的属性(attributes),这可能导致自定义元素的属性无法正确应用。
  • 在服务器端渲染(SSR)时,React 处理自定义元素的方式可能与客户端渲染(CSR)不一致,导致渲染差异。

改进:

  • 在服务器端渲染(SSR)时,React 会将传递给自定义元素的 props 是原始数据类型(string, number,)或者是 true 的时候渲染为 HTML 属性。对于非原始数据类型(如 object, symbol, function)或是 false 的props,React 19 会忽略它们,不渲染为 HTML 属性。
  • 在客户端渲染时,React 会根据自定义元素实例上的属性来处理传递给自定义元素的 props。如果传递的 props 与自定义元素实例上的属性匹配,React 会将它们作为属性进行赋值。否则,React 会将它们作为HTML 属性进行赋值。
相关推荐
好开心338 小时前
axios的使用
开发语言·前端·javascript·前端框架·html
wakangda11 小时前
React Native 集成原生Android功能
javascript·react native·react.js
北京_宏哥12 小时前
python接口自动化(四十)- logger 日志 - 下(超详解)
python·前端框架·自动化运维
CoderLiu12 小时前
用Rust写了一个css插件,sass从此再见了
前端·javascript·前端框架
秃头女孩y17 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js
前端小小王1 天前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 天前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
不是鱼1 天前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
川石教育1 天前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
飞翔的渴望1 天前
antd3升级antd5总结
前端·react.js·ant design