React 19 API 迁移实战:OpenMontage 组合模式技能中的 ref-as-prop 与 use() 取代 useContext
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
本文聚焦 OpenMontage 仓库中
.agents/skills/vercel-composition-patterns技能规则react19-no-forwardref.md所定义的 React 19 API 变更规范:在 React 19 中ref升级为常规 prop(不再需要forwardRef包装),同时use()取代useContext()。读完本文,你将掌握 React 18 向 19 迁移时组件签名与 Context 读取方式的标准改法、use()区别于useContext()的条件调用特性,以及如何在复合组件(compound components)模式中落地这套新 API,并了解当前仓库(Remotion 合成器仍运行在 React 18 上)的实际迁移前提。
规则定位与适用前提:React 19+ only
该规则文件位于.agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md,frontmatter 声明如下元数据:
| 字段 | 值 |
|---|---|
title | React 19 API Changes |
impact | MEDIUM |
impactDescription | cleaner component definitions and context usage |
tags | react19, refs, context, hooks |
在技能总入口 SKILL.md 中,React 19 APIs被列为第四类规则(优先级 4,前缀react19-,影响级别 MEDIUM),与 Component Architecture、State Management、Implementation Patterns 并列;_sections.md同样将其定义为独立分区,描述为 "React 19+ only. Don't useforwardRef; useuse()instead ofuseContext()."
规则开篇即给出硬性前提:
⚠️ React 19+ only.Skip this if you're on React 18 or earlier.
这意味着该规则不是对所有代码库一律生效的通用建议,而是针对已升级到 React 19 的项目。若项目仍停留在 React 18 或更早版本,forwardRef与useContext依然是正确写法,切勿盲目套用。
核心变更一:ref 成为常规 prop,forwardRef 退出历史舞台
在 React 18 及更早版本中,函数组件默认不接收ref(ref不属于 props),必须借助forwardRef显式"转发"。这在包装第三方输入组件(如TextInput)时产生大量样板代码:
Incorrect(React 19 中继续使用 forwardRef):
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => { return <TextInput ref={ref} {...props} /> })Correct(React 19 中 ref 作为常规 prop):
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) { return <TextInput ref={ref} {...props} /> }React 19 将ref正式纳入 props 体系,函数组件可以直接从 props 中解构出ref并透传给子组件。带来的收益是:
- 组件签名更"扁平"、更直观,少一层
forwardRef包装; - TypeScript 类型更简洁,
Props & { ref?: React.Ref<TextInput> }直接声明在函数参数上; - 消除了
forwardRef与泛型组合时的类型摩擦(forwardRef<TextInput, Props>的泛型顺序历来容易写错)。
核心变更二:use() 取代 useContext()
React 19 引入的新 Hookuse()可以读取 Context,替代useContext():
Incorrect(React 19 中继续使用 useContext):
const value = useContext(MyContext)Correct(React 19 中使用 use 替代 useContext):
const value = use(MyContext)两者在"读取 Context 值"这一行为上等价,但use()带来一个关键差异——可以在条件分支中调用:
use()can also be called conditionally, unlikeuseContext().
useContext()作为 Hook 必须遵守"只在组件顶层调用"的规则,不能出现在if、循环或嵌套函数中;而use()是面向"资源读取"设计的,它打破了这一限制,允许在条件逻辑内部按需读取 Context(同理也支持读取 Promise 等资源)。在复合组件内部,这为"按需取用 state / actions / meta"提供了更灵活的编写方式。
组合模式中的实际落地:复合组件直接消费 Context
该规则并不是孤立存在的,它与技能中其他规则(尤其是复合组件模式architecture-compound-components.md、状态提升state-lift-state.md、通用 Context 接口state-context-interface.md)构成完整体系。在技能的长文档 AGENTS.md 第 4 节中,React 19 API 被直接应用到复合组件实现里:
const ComposerContext = createContext<ComposerContextValue | null>(null) function ComposerProvider({ children, state, actions, meta }: ProviderProps) { return ( <ComposerContext value={{ state, actions, meta }}> {children} </ComposerContext> ) } function ComposerInput() { const { state, actions: { update }, meta: { inputRef }, } = use(ComposerContext) return ( <TextInput ref={inputRef} value={state.input} onChangeText={(text) => update((s) => ({ ...s, input: text }))} /> ) }注意其中的两处 React 19 语法:
<ComposerContext value={...}>直接作为 Provider 使用——React 19 允许把 Context 本身当作 Provider 组件(Context.Provider简写为<Context>),不再强制写<ComposerContext.Provider>;use(ComposerContext)读取共享状态——子组件(ComposerInput、ComposerSubmit等)通过use()消费由 Provider 注入的{ state, actions, meta }三元组,实现"组合内部、共享 Context、而非逐层传 props"的复合组件原则。
对于需要"视觉上位于组件框之外、但仍要访问共享状态"的自定义组件(如对话框底部提交按钮、消息预览),同样借助use()读取:
function ForwardButton() { const { actions: { submit }, } = use(ComposerContext) return <Button onPress={submit}>Forward</Button> }这正是状态提升规则所强调的核心洞察:需要共享状态的组件不必在视觉上互相嵌套,只要处于同一 Provider 边界内即可。use()把这种跨视觉边界的读取写法进一步简化。
与仓库现状的对应关系:当前 Remotion 合成器仍在 React 18
需要诚实说明适用前提:规则标注 "React 19+ only",而 OpenMontage 仓库当前的 Remotion 合成器remotion-composer在 package.json 中声明依赖为"react": "^18.2.0"与"react-dom": "^18.2.0"(@types/react亦为^18.2.0)。也就是说:
- 当前仓库组件仍以 React 18 API 编写。例如 Root.tsx 中注册的
Explainer、HeroTitle、EndTag等 Composition 组件均使用React.FC与 props 解构的常规写法; - HeroTitle.tsx、ProgressBar.tsx 等场景组件也没有使用
forwardRef——React 18 下若确有 ref 透传需求仍需forwardRef,React 19 下则可直接以refprop 形式声明; - 因此
react19-no-forwardref规则在仓库中属于面向未来升级的迁移目标规范:当remotion-composer将依赖升级到 React 19 时,组件签名、Context 读取方式需要按本规则批量调整。
迁移检查清单
结合规则文件与技能整体规范(每个规则文件均包含"为什么重要 + 错误示例 + 正确示例 + 补充上下文"四要素,见 SKILL.md 的 "How to Use"),给出落地检查清单:
- 确认 React 版本:
package.json中react主版本为^19或更高时才启用本规则;仍是 18 及以下则跳过; - 扫描
forwardRef用法:搜索forwardRef<,将包装层移除,把ref并入 props 类型(Props & { ref?: React.Ref<T> }),并在函数体内直接透传; - 扫描
useContext用法:将useContext(MyContext)替换为use(MyContext); - 利用条件调用能力:审视原本因"Hook 不能在条件中调用"而被迫上提的状态读取逻辑,判断是否可用
use()收敛到更内聚的位置; - 结合复合组件改造:将规则与
architecture-compound-components、state-context-interface一并执行,用<Context value={...}>+use()重构共享状态读取,消除 render props 与布尔 prop 泛滥。
参考与继续阅读
- 规则原文:react19-no-forwardref.md
- 技能入口与规则目录:SKILL.md、README.md
- 完整长文档(含复合组件、状态提升、依赖注入的展开示例):AGENTS.md
- 分区定义:rules/_sections.md
- 仓库 Remotion 合成器(当前 React 18,未来迁移目标):remotion-composer/package.json、remotion-composer/src/Root.tsx
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考