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 带来的线上问题

相关推荐
ekskef_sef38 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr2 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js