685 字
3 分钟
useRef

ref 存的是「不用来渲染的值」#

const countRef = useRef(0)
countRef.current += 1 // 界面不会变

useRef(0) 返回一个对象,形状固定是 { current: 0 }。这个对象在组件生命周期里是同一个引用,可变的只有 .current。

它和 useState 的分工很清楚:值要出现在界面上就用 state,改它会重渲染;值只是被逻辑用一下,改它不该引起重渲染,就用 ref。计时器 ID、上一次的值、DOM 节点、第三方库实例,都属于后者。

拿到 DOM 节点#

function InputFocus() {
const inputRef = useRef(null)
useEffect(() => {
inputRef.current.focus()
}, [])
return <input ref={inputRef} />
}

要点有三个。挂载前 inputRef.current 是 null,所以读之前要判空。TypeScript 里写 useRef<HTMLInputElement>(null),让它知道将来装的是什么。要操作 DOM 就放在 Effect 里,不在渲染期间做。

如果不想为此起一个 Effect,用 ref 回调:

<div
ref={node => {
if (node) node.scrollIntoView()
}}
/>

React 在提交阶段调用它,参数是真实节点。

ref 回调可以返回清理函数#

React 19 起,ref 回调允许返回一个清理函数,卸载时 React 调用它:

<div
ref={node => {
const observer = new ResizeObserver(entries => {
console.log(entries[0].contentRect)
})
observer.observe(node)
return () => observer.disconnect()
}}
/>

旧写法是在回调里判断 node === null 来做清理。官方说将来会弃用「卸载时以 null 调用 ref 回调」这种做法(React 19 发布说明),新代码用返回清理函数的形式。

注意返回值的类型:ref={c => (instance = c)} 会被 TypeScript 拒绝,因为它返回了赋值结果。写成块体 ref={c => { instance = c }}。

把 ref 交给自己写的组件#

React 18 里必须先包一层 forwardRef:

// 18 写法
const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />
})

React 19 起 ref 就是一个普通 prop,直接收:

// 19 写法
function MyInput({ value, onChange, ref }) {
return <input value={value} onChange={onChange} ref={ref} />
}

forwardRef 还没有删除,但官方已标为弃用(forwardRef)。读旧代码时认识它,新建组件不用它。

一个例外:传给类组件的 ref 不是 prop,它指向组件实例。

要向父组件暴露自定义方法时,用 useImperativeHandle,见下一篇。

两件别做的事#

别在渲染期间读写 ref.current。渲染要纯:同样的 props 与 state 应该得到同样的 JSX。渲染里写 ref,在并发渲染下会读到中间值。要在渲染里计算东西,用 state 或在渲染中直接算。

别用 ref 驱动界面。ref 变了不触发重渲染,界面和数据就脱节了。

React 19 改了 useRef 的类型#

useRef() 不传参现在过不了类型检查,写 useRef(null) 或 useRef(undefined)。这是类型要求的收紧,不是运行时错误,旧代码里那行 useRef() 在运行时不报错(React 19 升级指南)。

第 14 篇,共 39 篇本文属于系列

useRef
https://www.choria.top/posts/hook-use-ref/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时