Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

hook:useRef

  • 用来记录一个值,可用 .current 属性取出
  • 核心特点: 改变 ref.current 不会触发组件重新渲染,引用在整个组件生命周期内保持不变

场景代码示例

  • 【场景 1】引用 DOM 元素(最常用):

    • 获取 DOM 元素的引用,用于操作 DOM

      // 场景:聚焦输入框 - 通过 ref 获取 DOM 元素
      function TextInputWithFocusButton() {
        const inputEl = useRef(null); // 初始值为 null
      
        const onButtonClick = () => {
          inputEl.current.focus(); // 通过 .current 访问 DOM 元素
        };
      
        return (
          <div>
            <input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
          </div>
        );
      }
      
    • inputEl.current 会被自动赋值为对应的 DOM 元素

    • 组件挂载后,inputEl.current 指向实际的 <input> 节点

    • 可用于:聚焦、滚动、测量尺寸、播放视频等 DOM 操作

  • 【场景 2】存储定时器 ID(常用):

    • 保存定时器 ID,用于清除定时器

      // 场景:清除定时器 - ref 存储 timer ID
      function Timer() {
        const [count, setCount] = useState(0);
        const timerRef = useRef(null);
      
        const startTimer = () => {
          if (timerRef.current) return; // 防止重复启动
          timerRef.current = setInterval(() => {
            setCount((c) => c + 1);
          }, 1000);
        };
      
        const stopTimer = () => {
          clearInterval(timerRef.current);
          timerRef.current = null;
        };
      
        useEffect(() => {
          return () => clearInterval(timerRef.current); // 组件卸载时清除
        }, []);
      
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={startTimer}>Start</button>
            <button onClick={stopTimer}>Stop</button>
          </div>
        );
      }
      
    • 如果用 state 存储 timer ID,每次 setState 会触发重新渲染

    • 用 ref 存储,修改 timerRef.current 不会触发渲染

  • 【场景 3】存储前一次的值(常用):

    • 保存上一次渲染时的某个值,用于对比

      // 场景:追踪前值 - 记录上一次的 count
      function Counter() {
        const [count, setCount] = useState(0);
        const prevCountRef = useRef();
      
        useEffect(() => {
          prevCountRef.current = count; // 每次渲染后更新
        });
      
        const prevCount = prevCountRef.current; // 取出上一次的值
      
        return (
          <div>
            <p>当前: {count}</p>
            <p>之前: {prevCount}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
          </div>
        );
      }
      
    • 初次渲染时,prevCountundefined

    • 后续渲染时,prevCount 保存的是上一次的 count

    • 可用于:检测值是否变化、计算差值等

  • 【场景 4】存储不需要触发渲染的可变值(常用):

    • 存储任何需要在渲染之间保持的值,但不需要触发重新渲染

      // 场景:记录点击次数 - 不需要显示,只用于统计
      function ClickTracker() {
        const clickCountRef = useRef(0);
        const [message, setMessage] = useState("");
      
        const handleClick = () => {
          clickCountRef.current += 1; // 修改 ref 不触发渲染
          console.log("Total clicks:", clickCountRef.current);
      
          if (clickCountRef.current >= 10) {
            setMessage("你已经点击了 10 次!"); // 触发渲染
          }
        };
      
        return (
          <div>
            <button onClick={handleClick}>点击我</button>
            <p>{message}</p>
          </div>
        );
      }
      
    • 适用于不需要在 UI 中显示的数据

    • 类似于类组件中的实例变量 this.xxx

  • 【场景 5】避免闭包陷阱(进阶):

    • 在回调函数中访问最新的状态值

      // 场景:获取最新的 count 值 - 避免闭包陷阱
      function Counter() {
        const [count, setCount] = useState(0);
        const countRef = useRef(count);
      
        // 每次 count 更新时,同步更新 ref
        useEffect(() => {
          countRef.current = count;
        }, [count]);
      
        const handleClick = () => {
          setTimeout(() => {
            // 3 秒后访问最新的 count
            alert("最新的 count: " + countRef.current);
          }, 3000);
        };
      
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={handleClick}>3秒后查看最新值</button>
          </div>
        );
      }
      
    • 如果直接在 setTimeout 中访问 count,会形成闭包,拿到的是旧值

    • 使用 countRef.current 可以始终获取最新值


useRef vs useState

特性useRefuseState
修改是否触发渲染❌ 不触发✅ 触发
值的访问方式ref.current直接访问
渲染间是否保持✅ 保持(引用不变)✅ 保持(值不变)
主要用途存储不需要显示的可变值、DOM 引用存储需要显示的状态
更新时机立即同步更新异步批量更新

选择原则:

  • 需要在 UI 中显示 → 用 useState
  • 不需要在 UI 中显示 → 用 useRef

常见应用场景总结

场景说明示例
引用 DOM 元素获取 DOM 节点,进行操作聚焦输入框、滚动、测量尺寸
存储定时器 ID保存 timer/interval ID,便于清除setTimeout、setInterval
存储前一次的值对比前后值的变化检测变化、计算差值
存储可变值不需要触发渲染的数据统计数据、缓存、标志位
避免闭包陷阱在异步回调中获取最新值setTimeout、事件监听器
存储函数实例存储不随渲染变化的函数引用第三方库实例、事件处理器

注意事项

  • ⚠️ 不要在渲染期间读写 ref.current,这会导致组件行为不一致

    // ❌ 错误:渲染期间修改 ref
    function Bad() {
      const ref = useRef(0);
      ref.current += 1; // 不要这样做!
      return <div>{ref.current}</div>;
    }
    
    // ✅ 正确:在事件处理或 effect 中修改
    function Good() {
      const ref = useRef(0);
      useEffect(() => {
        ref.current += 1; // 在 effect 中修改
      });
      return <div>Good</div>;
    }
    
  • ⚠️ 修改 ref.current 不会触发重新渲染

    // ❌ 不会更新 UI
    const countRef = useRef(0);
    const increment = () => {
      countRef.current += 1; // UI 不会更新
    };
    
    // ✅ 需要显示在 UI 中,用 useState
    const [count, setCount] = useState(0);
    const increment = () => {
      setCount(count + 1); // UI 会更新
    };
    
  • ref 在整个组件生命周期内保持同一个引用,适合存储跨渲染的可变值