> 技术文档 > useRef

useRef

下面,我们来系统的梳理关于 React useRef Hook 的基本知识点:


一、useRef 基础概念

1.1 什么是 useRef?

useRef 是 React 提供的 Hook 函数,用于创建可变的引用对象(ref object),其核心特性是:

  • 跨渲染周期持久化:存储的值在组件整个生命周期保持不变
  • 独立于渲染流程:修改 ref 不会触发组件重新渲染
  • 直接访问 DOM 节点:获取和操作 DOM 元素

1.2 useRef 与 useState 对比

特性 useRef useState 触发渲染 修改不触发重新渲染 修改触发重新渲染 存储值 当前值在 .current 属性 通过解构获得当前值 更新时机 同步更新 异步更新 主要用途 访问DOM/保存可变值 管理渲染相关的状态

1.3 基本语法

const refContainer = useRef(initialValue);
  • initialValue:ref 对象的初始值(可以是任意类型)
  • 返回值:一个带有 .current 属性的对象

二、useRef 核心特性

2.1 引用持久性

function RefExample() { const countRef = useRef(0); // 初始值为0 const handleClick = () => { countRef.current += 1; // 修改ref不会触发重渲染 console.log(`点击次数: ${countRef.current}`); }; return ;}
  • 特性:ref 对象在组件的整个生命周期中保持不变
  • 注意:修改 .current 属性不会触发重新渲染

2.2 访问 DOM 元素

function InputFocus() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); // 访问DOM方法 }; return ( 
);}
  • 工作流程
    1. 创建 ref 对象:const inputRef = useRef(null)
    2. 关联 DOM:
    3. 访问元素:inputRef.current 指向实际 DOM 节点

三、useRef 高级用法

3.1 存储组件实例值

function TimerComponent() { const timerRef = useRef(null); // 存储定时器ID useEffect(() => { timerRef.current = setInterval(() => { console.log(\'定时器运行\'); }, 1000); return () => clearInterval(timerRef.current); }, []); // ...}
  • 优势:避免将定时器ID等不参与渲染的值放入state

3.2 保存上一次的状态

function Counter() { const [count, setCount] = useState(0); const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = count; // 渲染完成后更新 }); // 无依赖数组,每次渲染后执行 return ( 

当前值: {count}

上一次值: {prevCountRef.current}

);}
  • 原理:利用 useEffect 在渲染后执行的特性

3.3 自定义 Hook 封装

function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); // 值变化时更新ref return ref.current; // 返回上一次的值}// 使用const prevCount = usePrevious(count);

四、性能优化与注意事项

4.1 避免滥用 useRef

  • 适用场景
    • 访问 DOM 节点
    • 存储不触发渲染的可变值
    • 保存前一次渲染的值
  • 不适用场景
    • 需要触发渲染的状态变化(使用 useState)
    • 派生状态(使用 useMemo)
    • 复杂状态逻辑(使用 useReducer)

4.2 与 forwardRef 结合使用

当需要向子组件传递 ref 时:

const FancyInput = React.forwardRef((props, ref) => { return ;});function Parent() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); }; return ( 
);}

4.3 useRef 在类组件中的等效

class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return 
; }}

五、常见问题与解决方案

5.1 ref 为 null 的问题

场景:在 useEffect 外访问 ref.current 可能为 null

function MyComponent() { const ref = useRef(null); // 错误:渲染期间 ref.current 可能尚未赋值 // console.log(ref.current); useEffect(() => { // 正确:在 useEffect 中访问 console.log(ref.current); }, []); return 
;}

解决方案:确保在组件挂载后(useEffect/事件处理中)访问 ref

5.2 条件渲染导致 ref 失效

function ConditionalRender() { const [show, setShow] = useState(false); const ref = useRef(null); useEffect(() => { if (show) { console.log(ref.current); // 显示时为元素 } }, [show]); return ( 
{show &&
内容
}
);}

解决方案:确保 ref 关联的 DOM 存在后再访问

5.3 在列表中使用 ref

function List() { const items = [1, 2, 3]; const refs = useRef([]); useEffect(() => { refs.current.forEach(ref => console.log(ref)); }, []); return ( 
    {items.map((item, index) => (
  • refs.current[index] = el} // 回调ref > {item}
  • ))}
);}

注意:使用回调 ref 动态管理列表项的 ref

六、案例

6.1 视频播放控制

function VideoPlayer() { const videoRef = useRef(null); const [playing, setPlaying] = useState(false); const togglePlay = () => { if (playing) { videoRef.current.pause(); } else { videoRef.current.play(); } setPlaying(!playing); }; return ( 
);}

6.2 滚动位置记录

function ScrollPosition() { const positionRef = useRef({ x: 0, y: 0 }); const divRef = useRef(null); const handleScroll = () => { positionRef.current = { x: divRef.current.scrollLeft, y: divRef.current.scrollTop }; }; const restorePosition = () => { divRef.current.scrollTo( positionRef.current.x, positionRef.current.y ); }; return ( 
<div ref={divRef} onScroll={handleScroll} style={{ height: \'200px\', overflow: \'auto\' }} > {/* 长内容 */}
);}

6.3 组件渲染次数统计

function RenderCounter() { const renderCount = useRef(0); renderCount.current += 1; return 
渲染次数: {renderCount.current}
;}

注意:此用法会导致开发模式下计数加倍(React严格模式)

七、总结与最佳实践

7.1 核心要点

  • 持久化存储:useRef 创建的引用在组件生命周期内保持不变
  • 非响应式:修改 ref 不会触发重新渲染
  • 访问 DOM:主要用途之一
  • 灵活存储:可存储任意可变值

7.2 最佳实践

  1. 避免在渲染期间修改 ref:可能导致不可预测行为
  2. 优先使用回调 ref 处理动态列表:确保 ref 正确更新
  3. 与 forwardRef 结合传递 ref:自定义组件暴露 DOM 节点
  4. 清理 ref 关联:在组件卸载时断开引用(可选)

7.3 使用场景总结

场景 示例 访问DOM 聚焦输入框、媒体控制 存储可变值 定时器ID、动画帧ID 保存上一次值 比较前后状态变化 不触发渲染的状态 跟踪组件内部状态但不影响UI 第三方库集成 需要直接操作DOM的库(如D3)