首页
/ Supabase React 19 组件实战:ref 作为普通 Props 与 use() 替代 useContext() 的迁移指南

Supabase React 19 组件实战:ref 作为普通 Props 与 use() 替代 useContext() 的迁移指南

2026-09-06 21:35:07作者:鲍丁臣Ursa

本文基于 Supabase 仓库内 Vercel 组件组合模式技能包中的规则文件 react19-no-forwardref.md,系统讲解 React 19 带来的两项 API 变化:ref 成为普通 prop(不再需要 forwardRef 包装)以及 use() 钩子取代 useContext()。Supabase 的 Studio 前端与 UI 组件包(packages/uipackages/ui-patterns)运行在 React 19 之上,读完本文你将能在自己的 React 19 项目中直接完成组件定义与 Context 消费方式的现代化改造。

规则定位与适用范围

这条规则出自仓库中为 AI 编码助手组织的技能文档 SKILL.md。该技能包定义了四大类 React 组件组合模式,按优先级组织为:

优先级 类别 影响程度 规则前缀
1 组件架构(Compound Components 等) HIGH architecture-
2 状态管理(Provider、状态提升等) MEDIUM state-
3 实现模式(显式变体、children 组合等) MEDIUM patterns-
4 React 19 APIs MEDIUM react19-

react19-no-forwardref 属于第四类,影响程度(impact)标注为 MEDIUM,描述为“更简洁的组件定义与 Context 使用方式”,标签涵盖 react19refscontexthooks

适用前提(原文档的核心警告):本规则仅适用于 React 19+。如果你的项目仍在 React 18 或更早版本,应跳过这条规则,继续保留 forwardRefuseContext() 的既有写法。

Supabase 仓库当前满足该前提。从 pnpm-workspace.yaml 的 catalog 定义可以看到:

react: ^19.2.6
react-dom: ^19.2.6
@types/react: ^19.2.14
@types/react-dom: ^19.2.3

各应用与组件包(如 apps/studio/package.jsonpackages/ui/package.json)通过 "react": "catalog:" 引用这一定义,Studio 的 AGENTS.md 也明确写明技术栈为 “Next.js pages router + TanStack Start(迁移中),React 19”。因此仓库内的组件代码应当逐步向 React 19 的新 API 收敛。

变化一:ref 成为普通 prop,不再需要 forwardRef

问题写法:forwardRef 包装

在 React 18 及更早版本中,函数组件要接收 ref 必须通过 forwardRef 高阶函数把 ref 从 props 中“剥出来”:

// 不推荐(React 19 下的遗留写法)
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
  return <TextInput ref={ref} {...props} />
})

forwardRef 带来的代价是:

  • 组件签名变成高阶函数形式,类型参数顺序反直觉(forwardRef<T, P> 中 ref 类型在前、props 在后);
  • 对外导出的组件与内部实现函数割裂,displayName、HOC 包装等处理变繁琐;
  • 无法像普通 props 一样在解构、展开、泛型约束中自然地处理 ref。

正确写法:ref as a regular prop

React 19 中 ref 被提升为普通 prop,函数组件可以直接在参数里声明并接收它:

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

关键点在于类型声明:在 props 类型上交叉一个可选的 ref?: React.Ref<TextInput>,组件既保持普通函数签名,又显式表达了 ref 的目标类型。组件内部把 ref 继续透传给底层的 TextInput,行为与 forwardRef 版本完全等价,但定义更干净。

仓库源码中的印证

从源码结构看,Supabase 的 UI 模式包中已经存在“把 ref 作为普通 prop 传递”的实现。例如 FilterBarContext.tsx 中,FilterBarHandle 相关的类型定义里直接以 props 形式声明了:

ref: React.Ref<FilterBarHandle>

这正是规则所倡导的模式:ref 不再经过 forwardRef 通道,而是像 valueonChange 一样在 props 接口中声明与流转。

同时应当注意仓库的迁移现状packages/ui 下大量源自 shadcn/ui 的组件(如 button.tsx)仍是 forwardRef 写法。这属于历史代码,并非 React 19 项目的目标形态——forwardRef 在 React 19 中依然被保留以保证向后兼容,不会报错,只是不再必要。判断标准是:新编写或重构的组件应使用 ref-as-prop,存量代码在触及该组件时顺带迁移即可

变化二:use() 取代 useContext()

问题写法:useContext

传统方式下,消费 Context 使用 useContext() 钩子:

// 不推荐(React 19 下的遗留写法)
const value = useContext(MyContext)

正确写法:use()

React 19 引入的通用 use() 钩子可以直接读取 Context:

// 推荐(React 19)
const value = use(MyContext)

两者在“读取 Context 当前值”这一场景下功能等价,但 use() 有一个决定性优势(原文档明确指出的能力差异):

  • useContext() 必须无条件调用,受 React Hooks 规则约束,写在 if 分支里会破坏 hooks 调用顺序;
  • use() 可以被条件调用。因为它不是按调用顺序维护内部槽位的传统 hook,而是在渲染过程中“按需读取”,所以可以安全地放在条件分支内。

这意味着诸如“仅在子组件挂载某一层时才消费某个 Context”的逻辑,过去需要拆分组件或用 useContextnull 返回值绕行,现在可以用条件式 use(MyContext) 直接表达。

此外 use() 还统一了对 Promise 和 Context 两种异步数据的读取入口(这是 React 19 的通用设计),因此从 useContext 切换到 use 也是面向未来 API 收敛的顺势迁移。

组合迁移实践:一次改到位的组件模板

把一个典型的“包装输入框 + 消费某个 Context”的组件迁移到 React 19 风格,完整模板如下:

import { use } from 'react'

type Props = {
  disabled?: boolean
  placeholder?: string
}

function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
  // 变化二:use() 取代 useContext(),且允许条件调用
  const settings = use(ComposerSettingsContext)
  if (settings.enabled) {
    // use() 可以出现在条件分支中
  }

  // 变化一:ref 是普通 prop,直接透传
  return <TextInput ref={ref} disabled={settings.disabled ?? props.disabled} {...props} />
}

迁移时的检查清单(对应规则文件的 impact 与 tags):

  1. 组件是否使用了 forwardRef?改为普通函数 + ref prop 类型声明;
  2. 是否存在 useContext(...) 调用?替换为 use(...)
  3. 替换后检查 ref 的类型参数是否与底层 DOM 元素/句柄类型一致(如 React.Ref<TextInput>);
  4. 确认项目 React 版本 ≥ 19,否则第 3 步的 ref prop 与条件式 use() 均不可用。

与其他组合规则的配合

react19-no-forwardref 不是孤立规则,它与 SKILL.md 中同目录的规则协同构成完整的组件设计体系:

换句话说,React 19 的两项 API 变化降低了“组合模式”的实施成本:ref 透传无需高阶函数、Context 读取可条件化,这让 architecture-state- 类别中那些强调组合与解耦的模式在 React 19 项目中更加自然。

小结

维度 React 18 及更早 React 19+(本规则推荐)
函数组件接收 ref forwardRef<T, P>((props, ref) => ...) 普通函数,ref 作为 props 声明
读取 Context useContext(MyContext),必须无条件调用 use(MyContext),可条件调用
组件签名复杂度 高阶函数 + 反直觉的类型参数顺序 普通函数签名,类型直观
适用版本 全部 仅 React 19+,低版本项目跳过

对于 Supabase 这类整体运行在 React 19.2 上的仓库,这条 MEDIUM 级别的规则是组件新代码与重构的默认约定:新组件一律用 ref-as-prop 定义、用 use() 消费 Context;存量的 forwardRef / useContext 写法(可见于 packages/ui/src/components/shadcn/ui 等目录)随功能迭代逐步收敛即可。

登录后查看全文
热门项目推荐
相关项目推荐