TypeScript useRef 类型怎么用
打开一个普通的 JavaScript 文件,写一段 React 代码:在页面上有个按钮,点它就聚焦输入框,是不是很简单?用 document.querySelector 选节点的写法,总觉得有点“硬”——要是页面结构改了,比如把输入框从第一个改成第二个,这行代码就容易出问题。要是用 React 的 useRef 呢?写 const inputRef = useRef(null),再给输入框加 ref={inputRef},按钮点击时写 inputRef.current?.focus(),就把节点和代码绑死了,不用找来找去。
但要是把这代码改成 TypeScript,编辑器可能会跳红波浪线。为啥?因为 TypeScript 得知道这个 current 是什么类型,不能让它随便是啥。比如刚才的 inputRef,它的 current 是个 HTMLInputElement 类型的节点,得告诉 TypeScript。

最常用的写法,是给 useRef 加个泛型参数。比如写 const inputRef = useRef<HTMLInputElement>(null); 这里的 HTMLInputElement 就是告诉 TypeScript,这个 ref 只能绑输入框,不能绑按钮、div 这类别的。要是不小心写了 inputRef.current?.click(),编辑器就会报错,因为 HTMLInputElement 没有 click 方法——这样就把错误拦在写代码的时候,比运行时才出问题强多了。
得注意一个小细节:useRef 初始化的时候,要是设为 null,后面的 current 可能是 undefined 吗?比如组件刚渲染的时候,DOM 节点还没生成,这时候 inputRef.current 就是 null。所以调用方法的时候,最好加个可选链 ?.,比如 inputRef.current?.focus(),不然直接写 inputRef.current.focus(),编辑器会说“可能是 null,不能直接调方法”。
那要是 ref 绑的不是 DOM 节点,而是别的东西呢?比如存个定时器的 ID,或者一个自定义的变量。比如写 const timerRef = useRef<NodeJS.Timeout | null>(null); 这里的泛型就可以填这个变量的类型,定时器 ID 是 NodeJS.Timeout,初始化是 null,和 DOM 节点的情况差不多。要是想存普通的数值,比如 const countRef = useRef<number>(0); 也一样,不用和 DOM 沾边,只要把泛型填对就行。
还有个容易踩的坑:要是 ref 绑的是组件,比如给一个自定义的子组件 MyComponent 加 ref,泛型得用组件的实例类型。比如子组件 MyComponent 有个 focus 方法,那父组件的 ref 就得写 useRef<HTMLInputElement & { focus: () => void }>(null); 或者要是用 class 组件,直接用 MyComponent 类的类型就行——但函数组件默认是没有实例的,得用 forwardRef 配合 useImperativeHandle 把方法暴露出来,不然 TypeScript 会报错“函数组件没有实例”。
举个生活化的例子:useRef 就像家里的遥控器,泛型就是告诉 TypeScript“这个遥控器只能开电视,不能开空调”。要是拿错了,TypeScript 就会提醒你,避免按错键(代码写错)。比如你本来想让遥控器开电视,结果按了空调键,TypeScript 就会跳红,不让你这么干,省得用的时候才发现不对。
总结一下,用 TypeScript 写 useRef,核心就是“告诉它你要存的是什么”:绑 DOM 就填对应的 HTML 元素类型,绑变量就填变量类型,绑自定义组件就填组件实例类型。加个可选链,别让 null 捣乱,就能把这个工具用得顺顺当当,少出错误。