useRef 都有哪些使用场景?

分类: 编码创建于: 5/31/2025

useRef 在 React 中的主要使用场景

📌 1. 访问 DOM 元素

最常见的用法,用于直接操作 DOM 节点(如聚焦输入框、测量元素尺寸):

1function TextInput() {
2  const inputRef = useRef(null);
3
4  const focusInput = () => {
5    inputRef.current.focus(); // 手动聚焦输入框
6  };
7
8  return (
9    <>
10      <input ref={inputRef} type="text" />
11      <button onClick={focusInput}>聚焦输入框</button>
12    </>
13  );
14}

⏱️ 2. 存储计时器 ID

保存 setInterval/setTimeout 的返回值,用于后续清除:

1function Timer() {
2  const timerRef = useRef(null);
3
4  useEffect(() => {
5    timerRef.current = setInterval(() => {
6      console.log('定时器运行中...');
7    }, 1000);
8  
9    return () => clearInterval(timerRef.current); // 组件卸载时清除
10  }, []);
11}

📊 3. 保存前次状态

实现类似类组件中 prevProps/prevState 的功能:

1function Counter() {
2  const [count, setCount] = useState(0);
3  const prevCountRef = useRef();
4
5  useEffect(() => {
6    prevCountRef.current = count; // 更新为当前值
7  });
8
9  return (
10    <div>
11      当前值: {count}, 
12      前次值: {prevCountRef.current} 
13    </div>
14  );
15}

🧮 4. 缓存计算开销大的值

存储无需触发重新渲染的派生数据:

1function ExpensiveComponent({ data }) {
2  const cachedDataRef = useRef();
3
4  if (!cachedDataRef.current) {
5    // 仅当第一次渲染时计算
6    cachedDataRef.current = heavyComputation(data);
7  }
8
9  return <div>{cachedDataRef.current}</div>;
10}

🎬 5. 存储动画帧 ID

用于 requestAnimationFrame 动画控制:

1function Animation() {
2  const frameRef = useRef();
3
4  const animate = () => {
5    // 动画逻辑...
6    frameRef.current = requestAnimationFrame(animate);
7  };
8
9  useEffect(() => {
10    frameRef.current = requestAnimationFrame(animate);
11    return () => cancelAnimationFrame(frameRef.current);
12  }, []);
13}

📸 6. 保存第三方库实例

存储非 React 管理的对象实例(如 D3 图表、地图库):

1function Chart({ data }) {
2  const chartRef = useRef(null);
3  const chartInstance = useRef();
4
5  useEffect(() => {
6    if (chartRef.current) {
7      // 初始化图表
8      chartInstance.current = new ThirdPartyChart(chartRef.current, data);
9    }
10  
11    return () => chartInstance.current.destroy(); // 清理
12  }, [data]);
13
14  return <div ref={chartRef} />;
15}

🪢 7. 记录组件挂载状态

检测组件是否仍在挂载状态(避免更新卸载组件的状态):

1function DataFetcher() {
2  const isMountedRef = useRef(true);
3
4  useEffect(() => {
5    fetchData().then(data => {
6      if (isMountedRef.current) {
7        // 仅当组件挂载时更新状态
8        setData(data);
9      }
10    });
11  
12    return () => {
13      isMountedRef.current = false; // 卸载时标记
14    };
15  }, []);
16}

🧩 8. 控制自定义 Hook 内部状态

在自定义 Hook 中维护私有状态:

1function usePrevious(value) {
2  const ref = useRef();
3  useEffect(() => {
4    ref.current = value; // 更新前值但不触发渲染
5  }, [value]);
6  return ref.current; // 返回前次值
7}

💡 核心特性总结

特性说明
跨渲染周期持久化存储的值在组件重新渲染时保持不变
修改不触发重渲染更改 .current 值不会导致组件更新
类似实例变量类似类组件的实例字段(非状态数据)
同步更新修改后立即可读取新值(不同于 useState 的异步批量更新)

⚠️ 注意事项

  1. 不要用于渲染数据:修改 ref 不会触发重渲染
  2. 初始化避免函数调用useRef(computeExpensiveValue()) 会在每次渲染执行,应改用:
    1const ref = useRef(null);
    2if (ref.current === null) {
    3  ref.current = computeExpensiveValue(); // 惰性初始化
    4}
  3. 严格模式问题:开发环境下双重渲染可能导致 ref 被重置(使用 useEffect 可避免)

当需要操作 DOM 或存储与渲染无关的可变值时优先考虑 useRef,需要触发视图更新则使用 useState