前情提要 

useRef 是一个特殊的 Hook,它的值在组件重新渲染时不会被重新创建,这正是它的核心特性。

useRef 的特点:

  • 在组件重新渲染时保持引用不变
  • 修改 .current 不会触发重新渲染
  • 适合存储不需要触发渲染的可变值

使用场景:

  1. 访问和操作 DOM 元素
  2. 保存定时器、订阅等需要清理的资源
  3. 缓存前一次的值
  4. 避免闭包陷阱
  5. 性能优化 - 缓存昂贵对象
  6. 存储任何不需要触发渲染的可变数据

useRef 的渲染行为

function MyComponent() {
  const [count, setCount] = useState(0);
  
  // ✅ useRef 只在组件首次挂载时创建,后续渲染不会重新创建
  const countRef = useRef(0);
  const inputRef = useRef(null);
  
  // ❌ 普通变量每次渲染都会重新创建
  const normalVariable = { value: 0 };
  
  console.log('组件渲染了');
  console.log('countRef 是否是同一个对象:', countRef); // 始终是同一个对象
  console.log('normalVariable:', normalVariable); // 每次都是新对象
  
  const handleClick = () => {
    setCount(count + 1); // 触发重新渲染
    countRef.current += 1; // 不会触发重新渲染
  };
  
  return (
    <div>
      <p>State count: {count}</p>
      <p>Ref count: {countRef.current}</p>
      <button onClick={handleClick}>增加</button>
      <input ref={inputRef} />
    </div>
  );
}

什么时候使用 useRef

1. 访问 DOM 元素

function MyComponent() {
  const [count, setCount] = useState(0);
  
  // ✅ useRef 只在组件首次挂载时创建,后续渲染不会重新创建
  const countRef = useRef(0);
  const inputRef = useRef(null);
  
  // ❌ 普通变量每次渲染都会重新创建
  const normalVariable = { value: 0 };
  
  console.log('组件渲染了');
  console.log('countRef 是否是同一个对象:', countRef); // 始终是同一个对象
  console.log('normalVariable:', normalVariable); // 每次都是新对象
  
  const handleClick = () => {
    setCount(count + 1); // 触发重新渲染
    countRef.current += 1; // 不会触发重新渲染
  };
  
  return (
    <div>
      <p>State count: {count}</p>
      <p>Ref count: {countRef.current}</p>
      <button onClick={handleClick}>增加</button>
      <input ref={inputRef} />
    </div>
  );
}

2. 保存可变值(不触发重新渲染)

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null);
  
  const startTimer = () => {
    // 保存定时器 ID,清除时需要用到
    intervalRef.current = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
  };
  
  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };
  
  useEffect(() => {
    return () => {
      // 组件卸载时清除定时器
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []);
  
  return (
    <div>
      <p>时间: {seconds}秒</p>
      <button onClick={startTimer}>开始</button>
      <button onClick={stopTimer}>停止</button>
    </div>
  );
}

3. 保存前一次的值

function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  });
  
  return ref.current;
}

function MyComponent() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  
  return (
    <div>
      <p>当前值: {count}</p>
      <p>前一次的值: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

4. 避免闭包陷阱

function ChatRoom() {
  const [message, setMessage] = useState('');
  const latestMessageRef = useRef('');
  
  // 更新 ref 以保持最新值
  latestMessageRef.current = message;
  
  useEffect(() => {
    const timer = setInterval(() => {
      // 使用 ref 获取最新值,避免闭包陷阱
      console.log('最新消息:', latestMessageRef.current);
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // 空依赖数组,但仍能获取最新值
  
  return (
    <input 
      value={message}
      onChange={(e) => setMessage(e.target.value)}
      placeholder="输入消息"
    />
  );
}

5. 性能优化 - 缓存昂贵的对象

function ExpensiveComponent() {
  const [count, setCount] = useState(0);
  
  // ✅ 使用 useRef 缓存昂贵的对象创建
  const expensiveObjectRef = useRef(null);
  
  if (!expensiveObjectRef.current) {
    expensiveObjectRef.current = new SomeExpensiveClass();
  }
  
  // ❌ 每次渲染都会重新创建(性能问题)
  // const expensiveObject = new SomeExpensiveClass();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

6. 强制更新组件

function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, []);
  return update;
}

// 或者使用 useRef
function useForceUpdateWithRef() {
  const forceUpdate = useRef(() => {});
  const [, setState] = useState({});
  
  forceUpdate.current = () => setState({});
  
  return forceUpdate.current;
}

useRef vs useState 对比

function Comparison() {
  const [stateValue, setStateValue] = useState(0);
  const refValue = useRef(0);
  
  const updateState = () => {
    setStateValue(prev => prev + 1); // ✅ 触发重新渲染
  };
  
  const updateRef = () => {
    refValue.current += 1; // ❌ 不触发重新渲染
    console.log('Ref value:', refValue.current);
  };
  
  return (
    <div>
      <p>State: {stateValue}</p>
      <p>Ref: {refValue.current}</p>
      <button onClick={updateState}>更新 State</button>
      <button onClick={updateRef}>更新 Ref</button>
    </div>
  );
}

    Logo

    Agent 垂直技术社区,欢迎活跃、内容共建。

    更多推荐