React 19 Beta新内容前瞻合集(快速版)

发布于:2024-05-06 ⋅ 阅读:(32) ⋅ 点赞:(0)

前言

React v18 发布后,带来了以并发特性为主的各种新 API ( startTransition / useDeferredValue 等 )、新运作模式、及 stream SSR 上的改进等,其相比 React v17 像是一个增量的升级版。

而 React v19 则不然,包含了 大量 细小的、或破坏性的变更。

本文将快速前瞻 React v19 到目前为止( React v19 Beta )的所有变化,部分内容由于涉及层层历史背景,此处只进行简单介绍。

我们默认读者拥有 React <= v18 和 React v18 canary 版本的所有前置知识。

由于 React v18 canary 已迭代很久,本次更新中的很多内容属于历史已公布的内容,故不会重复做展开介绍。

正文

如阅读时间不足,请只关注重点内容,不重要的内容很少会在未来中使用到。

API 变更

新 API :useActionState

此 API 原名为 useFormState ,主要用于联动原生 <form /> 表单提交,现代开发更多使用高级表单库,故此特性不重要。

新 API :useFormStatus

此 API 与 useFormState 联动使用,现代开发更多使用高级表单库,故此特性不重要。

新 API :useOptimistic

通过此 API 派生其他的 state ,从而在提交动作时,立即乐观更新此值来优化用户视觉上的交互体验。

要实现乐观更新,必然要编写许多额外逻辑,同时现代请求库(如 react-querySWR 等)早已提供了乐观更新的功能。然而更多时候,我们没有精力和工时来提升用户体验,过度提升体验反而会造成更多冗余的乐观更新逻辑导致后续维护困难,展示 loading 已经足够,故此 API 不重要,若不是有心为之,则很难用到。

新 API :use

最重要的 API ,具有两种功能:

  1. 等待 Promise

    • Suspense 内,使用 use(Promise) 获取异步数据,无需自行 throw Promise
    • 在 RSC 和各种 React 状态管理库中已被广泛使用。
    • polyfill 版本已被广泛使用( 等同旧手动触发 Suspense 行为 )。
  2. 代替 useContext()

    • 无视 hooks 不能出现在条件判断内的规则,允许条件性的获取 Context 值。

目前此 API 已经在 React v18 canary 、RSC 、各种基础库内,被广泛使用,是使用最广泛、最重要的 API 之一。

在未来:

  1. 涉及到处理 Promise 数据时:

    • 若想联动 Suspense ,优先使用请求库内和 Suspense 相关的获取数据方法(如 useSuspenseQuerySWR Suspense ),避开直接使用 use 造成冗余逻辑。
    • 若不想联动 Suspense ,和以前一样请求库 + 展示 loading 态即可,无需使用 use(Promise)
    • 若为 RSC ,直接使用 use 便捷获取数据即可(若涉及较强的请求设计,如类型支持、同构等,仍需编写较多逻辑)。
  2. 涉及到获取 Context 数据时 :直接使用 use 代替 useContext ,这使得以下代码成为可能:

      if (condition) {
        value = use(ContextA)
      } else {
        value = use(ContextB)
      }
    
新 API :preXXX 预加载资源

分类来看:

  1. preconnectprefetchDNS 等 API 将插入 <link rel="preconnect" /> 标签的行为命令化,但由于预加载时机越早越好,等到 React 应用运行后,已经错过了 preconnect 的最好时机,所以编写至 HTML 内或 SSR 仍然是首选方案,此类 API 不重要。

  2. preinit / preload 等 API 将 脚本、样式 的加载命令化,但现代项目开发时已经内部模块化,使用 await import() 等懒加载行为已足够,故除了需要加载外部 脚本、样式 外,此 API 也不重要。

新 API :onCaughtError 全局错误捕获

这给 React 在全局 ReactDOM.createRoot 挂载时开放了一个全局捕获错误的出口:

ReactDOM.createRoot(document.getElementById('root')!, {
  onCaughtError: (error, errorInfo) => {
    // ...
  },
})

这可以避免在 ErrorBoundary 内部收集错误,而在全局更清真的统一处理(此处只是单纯报告错误,涉及到复杂的 ErrorBoundary 错误恢复和重试,仍然需要编写大量代码)。

注:不可以捕获 React 内的异步逻辑错误。

特性变更

新特性 :ref 将成为 props

在以前,涉及到 ref 时,因为要使用 forwardRef 并编写大量相关类型,故大多数情况我们会使用回调,回避掉使用 ref ,或使用其他 props 的名字传递 ref ,从而避免 forwardRef 的出现。

在 React v19 中, ref 将成为一个普通的 props ,可以直接在子组件内通过 props.ref 获取到,这是一个开发体验上的较大进步,但请不要滥用 ref 传递,更多时候我们只是为基础组件提供 ref ,或为外部提供较多主动操作方法时才会使用 ref

注:这并不代表 forwardRef 的消失,forwardRef 仍然有效;在历史兼容上,这可能会使得 ...props 的展开中存在意外的 ref ,但综合来看影响不大。

新特性 :Context 等价于 Context.Provider

这是一个开发体验上的优化,以前我们需要提供 Context 时,需要编写为 Context.Provider ,现在直接使用 Context 即可。

注:Context.Provider 仍然有效。

新特性 :<head /> 元信息将被提升

在以前,我们希望在某个组件内提供页面 <head /> 元信息时,往往使用 react-helmet-async ,而现在 React 将自动提升这些元信息标签(如 <title> 等),将其移动渲染至 <head></head> 内,而不保留在原处。

考虑到 SSR 的完备性,使用第三方库插入元信息仍然更好,所以此特性不重要。

新特性 :带有 加载优先级 的 <script /><style /> 等标签

对于使用 webpack 等打包工具进行模块化开发的现代,除 特别的样式覆盖 或 异步分包、构建工具缺陷 等原因导致对顺序出现 workaround 的需求外,一般我们在业务项目内不会接触到手动管理标签的情况,故此特性不重要。

需要关注的其他变化

回调函数形式的 ref 返回值将变为清理函数

由于 ref 回调函数使用概率较小(一般在 动画 或 获取布局 等场景下才使用),请格外留意不要在此函数内随意返回值。

React UMD 版本已被删除

仍有不少项目希望通过 external React UMD 的方式进行一些依赖外置化加载的设计,但未来 esm 是大势所趋,故 React v19 已删除 UMD 版本。

如还想通过外置形式使用 React ,需改为 esm 的 <script type="module"> 方式使用:

<script type="module">
  import React from "https://esm.sh/react@beta"
  import ReactDOM from "https://esm.sh/react-dom@beta/client"
  ...
</script>
内部 SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 变量已改名

获取 React 内部信息的出口 SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 已被重命名为 _DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ,虽然 SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 名字上不推荐用户使用,但迄今为止该出口仍被不少库(如 可视化 相关)使用 。

故当此变量改名后,一些第三方库不更新的话,会影响到使用,需要多加关注。

useRef 必须传入默认值

React v19 要求 useRef(defaultValue) 传入一个默认值,这会影响到所有历史 useRef() 未设定默认值的调用写法。

在以前,我们往往会通过 useRef<HTMLInputElement>(null!) 技巧 避免类型提示为空,因为此处 ref 必定存在,若存在默认值或采用技巧,则不存在此变动带来的问题,但相当一部分人并不会采用 React TypeScript 技巧 ,请多加留意。

各种不重要的小变化

API 相关
  • ReactDOM.render / ReactDOM.hydrate 旧版渲染应用 API 已被删除链接

  • useDeferredValue 支持设定默认值( 链接

  • React.createFactory 已被删除( 链接

  • react-test-renderer/shallow 导出位置已被更改( 链接

  • unmountComponentAtNode 旧版卸载节点 API 已被删除链接

  • ReactDOM.findDOMNode API 已被删除链接

特性相关
  • 更好的 web components 支持( 链接

    注:这是一个较瞩目的新特性,但大多数人很少使用 web component ,故只有特定用户群体才值得去关注。

体验相关
  • 水合出现差异时,报错信息更友好( 链接 )。

  • 优化了控制台错误打印的格式( 链接

  • useLayoutEffect 在服务端运行的警告已被删除( 链接

Class 组件相关
  • Class 组件的 propTypes / defaultProps 已被删除链接

  • Class 组件的 getChildContext 已被删除( 链接

  • Class 组件的字符串 ref="string" 已被删除链接

函数组件相关
  • 函数组件的 render() 渲染方式已被删除( 链接
测试相关
  • act 函数导出位置已转移至 react链接

  • react-test-renderer 已弃用( 非删除, 链接

    注:此包以前可能用于一些简单的组件测试,但现在已被弃用,请在所有 React 的测试中统一使用 RTL ( @testing-library/react ),这将是官方推荐的唯一测试库。

类型相关
  • ReactElement 类型被调整( 链接

  • 限定 React JSX 类型的命名空间( 链接

    注:为了避免各种 JSX 的类型在全局冲突,Vue v3.4 也限制了自己的命名空间。

  • useReducer 类型改进( 链接

新 React 版本信息

伴随 React v19 Beta 的发布,React v18.3 也一并发布。

React v18.3

相比最后一个 React v18 的版本 v18.2v18.3 添加了一些警告提示,便于尽早发现问题,从而在升级 React v19 时更容易。

由于 React v19 包含的各种小变化数量较多,仅仅依靠 React v18.3 中的提示可能仍力不从心,如需更容易的升级,你可能还需要依靠各种针对 React v19 的 eslint 规则和 codemod 来达成升级预期。

React v20

目前已知内容:

  1. React compiler (编译优化器,如 React forget )不等于 React v19

  2. Activity ( 原 Offscreen )可能在 React v20 推出。

总结

关于 RSC

由于 RSC 已经由 Next.js 掌控,同时目前社区各种自制 RSC 框架各自为战(特性与实现七零八落),仍在不断发展中,最终 RSC 会怎样,其 发展方向、拥有特性 等一切与 Next.js 对齐即可,使用 RSC 目前的不二之选仅为 Next.js ,故文本未谈及 RSC 相关内容,如有兴趣可自行了解(如 taintpostponeuse server 、stream 数据传输支持 等)。

升级 React v19

在以往的 React v16v17v18 的升级中,我们没有遇到太多升级阻力,更多的是增量特性升级,但 React v19 包含的小变化较多,对于较大项目的升级可能会较困难(由于 React v19 仍未正式发布,故我们不断期待更多升级经验的分享与学习)。

参考资料

以下链接为 以上内容 中 一部分的相关参考资料(仅包含可访问的链接),若无额外精力,无需关注以下内容:

  • 博文:React v19 Beta 发布( 链接

  • 博文:React v19 升级指南( 链接

  • 变更日志:React v18.3 Release Changelog ( 链接

  • 原始 PR :删除 UMD 构建( 链接

  • React v19 支持 web component 的设计与讨论 RFC ( 链接

  • 原始 PR :React v18.3链接

  • 原始 PR :重命名 SECRET INTERNALS 变量名 ( 链接

  • 原始 PR :删除 Class 组件的 defaultProps链接

  • 原始 PR :改进水合不匹配时的警告内容( 链接

  • 原始 PR :useActionState链接

  • 原始 PR :ref 调整为普通 props ( 链接