☰
React 19 API 变更实践指南:以普通 props 取代 forwardRef、用 use() 取代 useContext(Dinero.js 示例仓库迁移参考)
2026/10/9 5:28:19 网站建设 项目流程
  • 金融科技

【免费下载链接】dinero.js

Create, calculate, and format money in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/di/dinero.js
点击查看免费下载

本篇技术指南围绕本仓库中 React 19 API Changes 规则文档展开,讲解 React 19 引入的两项核心 API 变更——ref作为普通 prop 传递、use()取代useContext()——并结合仓库内多个基于 React 19 与 Dinero.js 的真实示例工程(定价页、购物车、费用分摊等)给出可直接落地的改写方法。读完本文,你将掌握如何在 React 19 下移除forwardRef包装、以 props 方式接收ref,并学会用use()读取 Context(支持条件调用),从而写出更简洁、类型更清晰的组件定义。

适用前提:本文仅面向 React 19+

文首需要明确:本文涉及的所有写法变更都只在 React 19+ 生效。如果你仍在使用 React 18 或更早版本,请直接跳过本文——在旧版本中forwardRef依然是唯一官方推荐的 ref 转发方式,useContext也仍然是读取 Context 的标准 Hook,强行套用新写法会导致运行时行为不符合预期。

判断是否适用新写法,最直接的依据是检查项目的package.json依赖声明。本仓库中五个 React 示例工程(cart-react、pricing-react、expense-splitter、invoice-builder、portfolio-tracker)的依赖均声明为"react": "^19.0.0"、"react-dom": "^19.0.0",且配套"@types/react": "^19.0.0"。这意味着这些工程完全处于 React 19 的 API 语境之下,理应遵循新写法。

ref 的新身份:从 forwardRef 包装到普通 prop

在 React 19 之前,函数组件默认不持有实例,要想把父组件传来的ref转发到内部 DOM 节点或子组件,必须用forwardRef把组件包一层。这带来了两个问题:一是组件定义多了一层包装、可读性下降;二是forwardRef与其它 props 分离,类型表达不够直观。

React 19 将ref改为普通 prop:函数组件可以直接在 props 中接收ref,不再需要任何包装。

错误写法:继续使用 forwardRef(React 19 中不推荐)

下面的写法在 React 18 中是正确的,但在 React 19 中属于过时模式——组件被forwardRef包裹,ref作为第二个参数而非 props 传入:

const ComposerInput = forwardRef<TextInput, Props>((props, ref) => { return <TextInput ref={ref} {...props} /> })

正确写法:ref 作为普通 prop 接收

React 19 下,直接声明一个接收ref的普通函数组件即可,ref与其它 props 一起解构,再原样转交给内部的TextInput:

function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) { return <TextInput ref={ref} {...props} /> }

两个关键点值得展开:

  1. 类型定义:Props & { ref?: React.Ref<TextInput> }把ref声明为可选的React.Ref<TextInput>,类型签名明确表达了"转发到TextInput实例"的语义。React.Ref同时覆盖RefObject与回调 ref 两种形态,因此useRef创建的 ref 对象和(node) => {...}回调都能直接赋值。
  2. 透传:<TextInput ref={ref} {...props} />中ref与其余 props 一并展开传给原生组件,行为与旧版forwardRef完全等价——父组件拿到的仍然是TextInput的实例。

移除 forwardRef 后组件更"普通"

去掉forwardRef包装后,组件就是一个普通的函数组件,可以像其它组件一样参与组合与复用。以本仓库 pricing-react 工程为例,其所有组件(pricing-toggle.tsx、seat-slider.tsx、tier-card.tsx)均采用"纯函数组件 + props 驱动"的定义方式,例如:

export function PricingToggle({ monthlyBilling, onToggle, }: PricingToggleProps) { // 直接返回 JSX,无任何包装 }

甚至在 tier-card.tsx 内部,MonthlyPrice、YearlyPrice也是直接以普通函数组件形式定义、按需渲染。可以看到,React 19 把ref并入 props 后,整个组件体系可以统一为"普通函数 + props",不再存在"普通组件"与"ref 转发组件"两种形态的割裂——这正对应规则文档中 impact 描述所言的cleaner component definitions(更干净的组件定义)。

use() 取代 useContext:读取 Context 的新方式

React 19 同时推出了新的 Hookuse(),用于读取 Context 的值,取代旧的useContext。

错误写法:继续调用 useContext

const value = useContext(MyContext)

正确写法:改用 use()

const value = use(MyContext)

两者返回值完全一致,均返回MyContext的当前值(未匹配到 Provider 时返回 Context 的默认值),因此迁移成本极低:只需把导入与调用处的useContext改为use即可。注意此时use的实参是 Context 对象本身,不是 Provider。

use() 独有的能力:支持条件调用

use()相比useContext()最重要的差异是:use()可以在条件分支、循环等位置调用,而useContext()必须无条件地置于组件顶层。

function FeatureBanner() { // 只有满足条件时才读取 Context,这在 useContext 下是不允许的 if (someCondition) { const theme = use(ThemeContext) // ... } return null }

这一特性直接来自 React 19 对use的设计:它本质上是"读取资源的值"的通用原语,既支持 Context,也支持 Promise 等资源。由于可以条件化调用,它在代码组织上比useContext更灵活——这也是规则文档特别强调use()can also be called conditionally 的原因。

在 Dinero.js 示例仓库中的工程实践与迁移要点

本仓库的示例工程为这套新 API 提供了真实的落地环境:所有示例应用 React 19 + Vite + TypeScript,并在 UI 层调用dinero.js完成金额计算与格式化(参见 pricing-react 的 money 工具模块,内部统一导出dinero、multiply、allocate、subtract、isZero等函数)。对这类工程执行 React 19 API 迁移时,可以按以下清单自查:

检查项React 18 时代React 19 时代
ref 转发forwardRef((props, ref) => ...)组件 props 中直接声明{ ref, ...props }
读取 ContextuseContext(MyContext)use(MyContext)
条件读取 Context不允许(违反 Hooks 规则)允许(use()可在条件中调用)
依赖版本react/@types/react18.xreact/@types/react^19.0.0

在实际工程中执行迁移,建议遵循以下步骤:

  1. 升级依赖:将react、react-dom与@types/react、@types/react-dom统一升级到^19.0.0(与本仓库五个示例工程一致)。
  2. 移除 forwardRef:搜索代码中的forwardRef导入与调用,将组件改写为直接解构ref的普通函数组件,并在 props 类型中补充ref?: React.Ref<...>字段。
  3. 替换 useContext:将useContext(MyContext)替换为use(MyContext);若原代码中存在"仅在某些分支需要 Context"的逻辑,可利用use()的条件调用能力把读取动作内联到分支中。
  4. 验证类型与行为:确认 ref 回调 / ref 对象拿到的实例类型未变,Context 默认值语义未变。

需要说明的是,仓库示例本身(如 expense-splitter 的 App.tsx、pricing-react 的 App.tsx)当前仅使用了useState管理状态,并未出现forwardRef或useContext的用法——这恰好符合 React 19 新范式的方向:以普通 props 传递一切数据(包括ref),组件结构保持扁平、可预测。

总结

React 19 的这两项 API 变更是对组件模型的一次简化:

  • ref成为普通 prop,forwardRef包装不再必要,组件定义更干净、类型更直观;
  • use()取代useContext(),并且支持条件调用,读取 Context 的代码约束更少。

对本仓库内以react: ^19.0.0为基线的示例工程(cart-react、pricing-react、expense-splitter、invoice-builder、portfolio-tracker)而言,遵循上述写法即可与 Dinero.js 的金额处理能力无缝配合:组件层采用 React 19 新 API 保持简洁,业务层由dinero.js的纯函数保证金额计算的精确与一致。若你的项目仍在 React 18 及以下,则继续使用forwardRef与useContext即可,待升级到 React 19 后再按本文清单迁移。

  • 金融科技

【免费下载链接】dinero.js

Create, calculate, and format money in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/di/dinero.js
点击查看免费下载
上一篇:MemOS 配置指南:MOSConfig 全组件字段解析、工厂模式与实战初始化方法
下一篇:PaddleNLP 指数移动平均(EMA)算子解析:ExponentialMovingAverage 的实现、原理与实战应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询