useRef
下面,我们来系统的梳理关于 React useRef Hook 的基本知识点:
一、useRef 基础概念
1.1 什么是 useRef?
useRef
是 React 提供的 Hook 函数,用于创建可变的引用对象(ref object),其核心特性是:
- 跨渲染周期持久化:存储的值在组件整个生命周期保持不变
- 独立于渲染流程:修改 ref 不会触发组件重新渲染
- 直接访问 DOM 节点:获取和操作 DOM 元素
1.2 useRef 与 useState 对比
.current
属性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 ( );}
- 工作流程:
- 创建 ref 对象:
const inputRef = useRef(null)
- 关联 DOM:
- 访问元素:
inputRef.current
指向实际 DOM 节点
- 创建 ref 对象:
三、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 最佳实践
- 避免在渲染期间修改 ref:可能导致不可预测行为
- 优先使用回调 ref 处理动态列表:确保 ref 正确更新
- 与 forwardRef 结合传递 ref:自定义组件暴露 DOM 节点
- 清理 ref 关联:在组件卸载时断开引用(可选)