React 19 正式发布了,还不快用起来!

引言

就在昨天(2024.12.5),React 团队在 react.dev/blog 上发表了帖子 react.dev/blog/2024/1... React 19 正式进入了 stable 状态

React 团队介绍了一些新的特性和 Breaking Changes,并提供了升级指南,

新特性

本部分中的代码示例均来自或来自于修改后的官方示例

修改状态的"新姿势"

官方用例里,提供了一个接口请求的异步更新状态的 Demo:

在组件 UpdateName 中,handleSubmit 中会请求接口,同时把状态设置为 pending,如果接口请求错误则展示 error

于是这里分别使用了 3 个 useState 去创建了 3 个状态,用于表示数据、错误和加载状态(isPending)

javascript 复制代码
// 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>
  );
}

useTransition

React 19 提供了一个新的 Hooks:useTransition

使用 useTransition 可以以更少的代码实现这个结合异步操作并更新状态的能力

useTransition 调用后会返回一个数组,数组的第一个变量表示当前的 pending 状态,第二个变量 startTransition 为一个函数

调用这个函数并传入一个异步的函数(包括网络请求等耗时的异步动作),isPending 则会被自动设置为 true,在函数结束后被设置回 false

javascript 复制代码
// Using pending state from 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>
  );
}

看到这里,你可能会意识到:这里没有提供对异常的处理,使用者还是需要自行处理错误啊?那不是脱了xxxx...

别急,我们继续往下看

useActionState

使用 useActionState,传入一个异步函数,hook 将返回 3 个变量,表示 error,action 和 pending 状态

官方用例如下:

dart 复制代码
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
    return null;
  },
  null,
);

结合这两个 hooks,基本就可以告别一些类似 useRequest 或自己封装的请求 hooks 了

异步资源和懒加载的"新姿势"

use 关键字

在之前的提案中,React 官方就已经公布了 "use" 关键字,用于处理异步资源和状态

我们再次熟悉一下 React 19 正式版本中对 "use" 关键字的使用介绍

use 支持传入一个 Promise,并返回被 Promise 包裹的类型 T,可以将异步状态在组件返回内转化为同步数据进行渲染,同时组件也会因为使用了 use 而被"传染上"异步特性,在使用此组件时,需要用 <Susbense 标签包裹

javascript 复制代码
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 不仅可以用来处理异步资源,还可以用来读取 Context,参考以下示例:

javascript 复制代码
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>
  );
} 

不知道你有没有发现,不同于 useContext hook,use 关键字支持在条件分支中被使用,这也弥补了很多 hooks 的使用缺陷

预加载资源

react-dom 提供了 prefetchDNSpreconnectpreloadpreinit 等 API,用于在某个时机预加载一步资源:

csharp 复制代码
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
  preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
  preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
  preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
  prefetchDNS('https://...') // when you may not actually request anything from this host
  preconnect('https://...') // when you will request something but aren't sure what
}

Form 操作

React 19 提供了对于 <form 标签的一些支持

react-dom 包导出了一个新的 Hook useFormStatus,用于在被 <form 标签包裹的子组件访问整个表单的状态:

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

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

// parent component
function DesignForm() {
  const [, startTransition] = useTransition();
  const handleAction = startTranstion(async () => {
    // ..do something async
  });
  
  return <form action={handleAction}><DesignButton /></form>
}

这样对于复杂表单,不需要引入 Antd 等组件库也能很方便的处理状态了

更详细的介绍请参考 react.dev/reference/r...

其他特性

Ref

React 19 中支持将 ref 在 props 中传递,因此你不必再使用 forwardRef

原文中提到,官方将在未来删除 forwardRef

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

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

另外,在使用回调的方式获取 ref 时,支持通过返回值"清理副作用",其用法类似 useEffect 的返回值

javascript 复制代码
<input
  ref={(ref) => {
    // ref created

    // NEW: return a cleanup function to reset
    // the ref when element is removed from DOM.
    return () => {
      // ref cleanup
    };
  }}
/>

Context

Context 可以直接作为一个标签使用了,无需再使用 <Context.Provider 的方式

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

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

实战

上面介绍了诸多理论性的东西,想要熟练使用这些特性,还需要亲身实践才行

使用 create-next-app 来新建一个 React 19 项目,在终端里输入命令:

React 官方已经逐渐废弃了 cra(create-react-app),在新的示例中都使用 create-next-app

当然你也可以选择其他脚手架和工具链,例如 vite,rsbuild 等

ruby 复制代码
$ npx create-next-app@latest

脚手架会提示你进行一些项目配置,在完成所有配置后,你将得到一个 React 19 的项目目录

打开项目目录,查看项目结构

安装依赖并启动 App

ruby 复制代码
$ pnpm i
$ pnpm run dev

打开 localhost:3000,看到项目已经成功跑起来了

总结

相比 React 18 发布时,推出了 Concurrent Mode 并大改架构,React 19 更像是一次小的、对于使用体验的升级

如果你的项目已经在生产环境接入了18,那么不妨试试19

当然在迁移过程中,还是需要注意阅读官方文档,避免 Breaking Change 带来的线上问题

相关推荐
小小小小宇4 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖5 小时前
http的缓存问题
前端·javascript·http
小小小小宇5 小时前
请求竞态问题统一封装
前端
loriloy5 小时前
前端资源帖
前端
源码超级联盟5 小时前
display的block和inline-block有什么区别
前端
GISer_Jing5 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js
让梦想疯狂5 小时前
开源、免费、美观的 Vue 后台管理系统模板
前端·javascript·vue.js
海云前端6 小时前
前端写简历有个很大的误区,就是夸张自己做过的东西。
前端
葡萄糖o_o6 小时前
ResizeObserver的错误
前端·javascript·html
AntBlack6 小时前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端