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

相关推荐
zhougl99637 分钟前
html处理Base文件流
linux·前端·html
花花鱼41 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_44 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript