尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

React + TypeScript Ref 类型完全指南:RefObject、RefCallback 与 Ref 联合类型(React 19 篇)

React + TypeScript Ref 类型完全指南:RefObject、RefCallback 与 Ref 联合类型(React 19 篇) React TypeScript Ref 类型完全指南RefObject、RefCallback 与 Ref 联合类型React 19 篇【免费下载链接】reactCheatsheets for experienced React developers getting started with TypeScript项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet本文以 React TypeScript Cheatsheet 仓库的 Ref 参考文档 为核心系统讲解types/react中与 ref 相关的三个核心类型——RefObjectT、RefCallbackT与RefT——的定义、使用场景与相互关联并结合仓库中的 Hooks、forwardRef/createRef、ComponentProps 等文档与源码印证实际用法。读完本文你将能准确为useRef、createRef、内联 ref 回调以及组件ref属性选型并写出类型安全的代码理解 React 19 中ref 即普通 prop的变迁对类型系统带来的简化。一、先看全景三个类型一张表types/react内置了三个密切相关的 ref 类型。理解它们如何拼合在一起是正确书写 ref 类型的关键——尤其在 React 19 中ref已经是函数组件的普通 prop这组类型的使用方式也随之发生了重要变化。类型它是什么何时使用RefObjectT一个带current: T字段的对象。useRef和createRef的返回类型。把它传给ref{…}以读写.current。RefCallbackT一个接收实例卸载时接收null的函数。内联ref{node …}回调。在 React 19 中可以返回清理函数。RefTRefCallbackT \| RefObjectT \| null \| null的联合类型。作为接收父组件传入 ref 时的prop 类型——父组件可能传任意一种形式。interface RefObjectT { current: T; } type RefCallbackT (instance: T | null) void | (() void); type RefT RefCallbackT | RefObjectT | null | null;三个类型的分工可以概括为RefObject是容器、RefCallback是回调、RefT是收口——当你只需要对外接收 ref 时永远用最宽的RefT。二、RefObjectTuseRef与createRef的返回类型RefObjectT是useRef和createRef的返回值类型。它的.current字段类型取决于你传入的初始值import { useRef } from react; const inputRef useRefHTMLInputElement(null); // ^? RefObjectHTMLInputElement | null const idRef useRef(0); // ^? RefObjectnumber仓库 Hooks 文档明确指出当前types/react中useRef始终返回RefObjectT且必须提供初始值返回的.current类型由初始值推导。2.1 传入null初始值由 React 托管.current当你显式传入泛型并以null作为初始值时React 会在挂载阶段替你写入 DOM 节点。TypeScript 将.current类型为T | null因此使用前必须做空值检查useEffect(() { inputRef.current?.focus(); }, []);在 Hooks 文档的 DOM ref 示例中给出了更严谨的守卫式写法——先抛出异常再放心使用function Foo() { const divRef useRefHTMLDivElement(null); useEffect(() { // ref.current 可能为 null元素可能是条件渲染的也可能忘记绑定 ref if (!divRef.current) throw Error(divRef is not assigned); // 此时 divRef.current 一定是 HTMLDivElement doSomethingWith(divRef.current); }); // 把 ref 交给元素让 React 替你管理 return div ref{divRef}etc/div; }如果确定divRef.current永远不会为 null也可以用非空断言null!绕过空值检查但要清楚这是在主动放弃类型安全一旦忘记给元素绑定 ref或 ref 元素被条件渲染就会在运行时抛错。2.2MutableRefObjectT已弃用的旧类型MutableRefObjectT仍然存在于types/react中仅出于向后兼容保留并标记为deprecated——请一律改用RefObjectT。这在 Hooks 文档的useRef一节中亦有明确说明。2.3 可变值 refReact 不托管.current若要跨渲染保存可变值且不希望改动触发重渲染直接传入初始值即可。此时.current由你手动读写React 不会介入function Foo() { const intervalRef useRefnumber | null(null); useEffect(() { intervalRef.current window.setInterval(() { /* ... */ }, 1000); return () { if (intervalRef.current ! null) clearInterval(intervalRef.current); }; }, []); return ( button onClick{() { /* 清理 intervalRef */ }} Cancel timer /button ); }这里用useRefnumber | null(null)明确表达了初始为 null、之后写入 number的联合类型配合空值检查即可安全读写。三、RefCallbackT节点挂载/卸载瞬间执行代码回调 ref 适合在 DOM 节点挂载或卸载的那一刻执行代码。回调在挂载时收到节点卸载时收到nulldiv ref{(node) { if (node) console.log(mounted, node); else console.log(unmounted); }} /3.1 React 19 的清理函数cleanup function在 React 19 中ref 回调可以返回一个清理函数——React 会调用它而不是像旧版那样再次以null调用回调。这使 ref 回调与useEffect的清理机制保持对称div ref{(node) { const observer new IntersectionObserver(/* ... */); observer.observe(node); return () observer.disconnect(); }} /如果回调没有返回任何内容React 会回退到旧行为卸载时以null再次调用该回调。从类型定义type RefCallbackT (instance: T | null) void | (() void)可以印证返回值是无返回值或清理函数二者的联合。四、RefT接收 ref 的 prop 类型RefT是你在接收ref 作为 prop 时应使用的类型因为调用方可能传入RefObject或回调中的任意一种import { Ref } from react; type FancyInputProps { ref?: RefHTMLInputElement; placeholder?: string; }; function FancyInput({ ref, placeholder }: FancyInputProps) { return input ref{ref} placeholder{placeholder} classNamefancy /; }在 React 19 中这样写就足够了——ref是普通 prop不再需要forwardRef包裹。仓库 patterns_by_usecase 文档中的FancyButton示例同样遵循这一模式import { Ref, ReactNode } from react; type Props { children: ReactNode; type: submit | button; ref?: RefHTMLButtonElement; }; export const FancyButton ({ ref, children, type }: Props) ( button ref{ref} classNameMyCustomButtonClass type{type} {children} /button );4.1 把 ref 转发到非根元素如果接收到的 ref 并不属于根元素仍然可以把它继续向下传递——只要T匹配RefT可以赋值给任意元素的refproptype LabelledInputProps { label: string; ref?: RefHTMLInputElement; }; function LabelledInput({ label, ref }: LabelledInputProps) { return ( label {label} input ref{ref} / /label ); }这里ref没有落在根元素label上而是传给了内部的input类型完全成立。4.2 与forwardRef的对比React 19 前/后对于仍维护 React ≤ 18 代码的读者仓库 forward-create-ref 文档给出了旧式forwardRef写法其ref参数类型是ForwardedRefTimport { forwardRef, ReactNode } from react; interface Props { children?: ReactNode; type: submit | button; } export type Ref HTMLButtonElement; export const FancyButton forwardRefRef, Props((props, ref) ( button ref{ref} classNameMyClassName type{props.type} {props.children} /button ));同一文档还给出 React 19 下的两条新路线用ComponentPropsWithRefinput直接继承原生元素全部 props含 ref或像上文那样用RefT显式声明。这也呼应了 ComponentProps 文档中的说明React 19 下ComponentPropsT通常已够用因为ref对函数组件而言就是普通 prop只有需要从展开中剥离ref时才用ComponentPropsWithoutRefT。五、useImperativeHandle与RefT的组合React 19 下useImperativeHandle直接接收refprop无需forwardRef。仓库 Hooks 文档给出了完整的双向类型推导示例// Countdown.tsx import { useImperativeHandle, Ref } from react; export type CountdownHandle { start: () void; }; type CountdownProps { ref?: RefCountdownHandle; }; const Countdown ({ ref }: CountdownProps) { useImperativeHandle(ref, () ({ // start() 在此处获得类型推导 start() { alert(Start); }, })); return divCountdown/div; };// 使用 Countdown 的父组件 import { useEffect, useRef } from react; import Countdown, { CountdownHandle } from ./Countdown.tsx; function App() { const countdownEl useRefCountdownHandle(null); useEffect(() { if (countdownEl.current) { // start() 在调用侧同样获得类型推导 countdownEl.current.start(); } }, []); return Countdown ref{countdownEl} /; }注意countdownEl的类型是RefObjectCountdownHandle | null因为以null初始化父组件调用前仍需空值判断这与本文 2.1 节的规则完全一致。六、相关类型速查除了上述三兄弟types/react还提供几个相关的 ref 类型逐一说明ForwardedRefT——旧式forwardRef渲染函数收到的ref参数类型。仅在你仍使用forwardRef时相关优先改用 prop 上的RefT。LegacyRefT——RefT的deprecated别名。字符串 refstring refs已不再受支持。ComponentRefT——某组件或元素所接受的 ref 类型例如ComponentRefinput就是HTMLInputElement。当你想拿到 ref 类型又不愿手写完整名称时很有用。RefAttributesT——{ ref?: RefT }这样的 props 形状。很少需要直接使用它被ComponentPropsWithRef交叉组合进来。配合 ComponentProps 文档中的ComponentPropsWithRefT等于ComponentPropsT外加 ref与ComponentPropsWithoutRefT剥离任何refprop你可以在继承原生元素 props与精确控制 ref 类型两种策略间自由选择这也是 forward-create-ref 文档中 Option 1ComponentPropsWithRef与 Option 2显式RefT两种写法的选型依据。七、泛型组件中的 ref 处理泛型组件由于泛型参数会阻断自动类型推导通常需要手动处理 ref。仓库 forward-create-ref 文档提供了三种方案其中 Option 1包装组件最直观直接通过 props 传入RefTinterface ClickableListPropsT { items: T[]; onSelect: (item: T) void; mRef?: React.RefHTMLUListElement | null; } export function ClickableListT(props: ClickableListPropsT) { return ( ul ref{props.mRef} {props.items.map((item, i) ( li key{i} button onClick{() props.onSelect(item)}Select/button {item} /li ))} /ul ); }这里React.RefHTMLUListElement的使用正是本文核心类型的直接应用它既接受父组件传RefObject也接受传回调。文档建议Option 1 通常已足够且更清晰确需forwardRef行为时用 Option 2重声明forwardRef需要同时支持泛型与完整forwardRef类型推导的进阶库场景用 Option 3调用签名。八、核心结论选型口诀useRef/createRef的返回值用RefObjectT内联回调用RefCallbackTReact 19 可返回清理函数组件对外接收 ref 一律声明为ref?: RefT。React 19 的关键变化ref成为函数组件的普通 propRefT直接可作 prop 类型绝大多数场景不再需要forwardRefForwardedRef与LegacyRef退居旧代码兼容位置。类型安全红线以null初始化的RefObjectT | null在使用前必须空值检查null!非空断言是主动放弃类型安全的逃生舱。延伸阅读仓库内的 HooksuseRef与useImperativeHandle实战、forward-create-refReact 19 新写法与旧式forwardRef对照、泛型 ref 三方案、ComponentPropsComponentPropsWithRef/WithoutRef选型以及 patterns_by_usecase包装/镜像 HTML 元素共同构成了完整的 React 19 TypeScript ref 知识体系。【免费下载链接】reactCheatsheets for experienced React developers getting started with TypeScript项目地址: https://gitcode.com/gh_mirrors/reactt/react-typescript-cheatsheet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表