首页 / React 19 入门教程 / useRef 引用值

React 19 入门教程

useRef 引用值

本教程共 50 篇 · 第 30 篇 · 更新于 2026-07-29 · 约 6 分钟阅读

ReactuseRefref可变值不触发渲染

30. useRef 引用值

本节目标:理解 ref 是一种”不触发渲染的状态”,掌握 useRef 的用法,知道什么时候该用 ref 而不是 state。

ref 是什么

useRef 返回一个对象 { current: 初始值 }。你可以随时读写 ref.current

import { useRef } from 'react';

function Demo() {
  const ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    console.log(ref.current); // 数字在变
  }

  return <button onClick={handleClick}>点击</button>;
}

点击按钮,ref.current 递增,但页面不会更新。因为修改 ref 不触发重新渲染

ref 是 React 追踪不到的”口袋”

React 的 state 变化会触发渲染。ref 变化完全静悄悄,React 不知道,也不关心。

ref vs state

refstate
返回值{ current: value }[value, setValue]
修改方式ref.current = newValsetValue(newVal)
修改后不触发渲染触发渲染
读取时机不要在渲染期间读随时读
值的可变性可变(直接改)不可变(必须 setState)
// state 版本:每次点击页面更新
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ref 版本:数字变了但页面不更新
function Counter() {
  const count = useRef(0);
  return (
    <button onClick={() => { count.current += 1; console.log(count.current); }}>
      点击(看控制台)
    </button>
  );
}

什么时候用 ref

存储定时器 ID

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  function start() {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
  }

  return (
    <>
      <p>{count}</p>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
    </>
  );
}

定时器 ID 不需要渲染,用 ref 存刚好。

记录上一次的值

function Chat({ message }) {
  const prevMessageRef = useRef();

  useEffect(() => {
    prevMessageRef.current = message;
  }, [message]);

  const prevMessage = prevMessageRef.current;

  return (
    <div>
      <p>当前:{message}</p>
      <p>上一条:{prevMessage}</p>
    </div>
  );
}

记录渲染次数

function RenderCount() {
  const count = useRef(0);
  count.current += 1;

  return <p>渲染了 {count.current} 次</p>;
}

ref 的注意事项

不要在渲染期间修改 ref

function Bad() {
  const ref = useRef(0);
  ref.current += 1; // 危险!渲染期间修改
  return <p>{ref.current}</p>;
}

渲染期间修改 ref 会导致不可预测的行为。在事件处理函数或 effect 里修改。

不要在渲染期间读取 ref

function Bad() {
  const ref = useRef(0);
  return <p>{ref.current}</p>; // 每次渲染可能不同
}

渲染期间读取 ref 会让 UI 不稳定。需要显示的值用 state。

ref 的核心原则

ref 用于存储”不影响渲染的数据”。如果需要显示在页面上,用 state。

ref 的初始值

// 初始值为 null(常用于 DOM 引用)
const ref = useRef(null);

// 初始值为数字
const count = useRef(0);

// 初始值为对象
const data = useRef({ x: 0, y: 0 });

// 惰性初始值(复杂计算只执行一次)
const ref = useRef(() => expensiveCalc());
// 注意:这样存的是函数,不是计算结果

惰性初始值的正确写法

要惰性计算初始值,用 useRef 配合条件判断,或者直接用 useState 的函数形式。

实战:秒表

function Stopwatch() {
  const [now, setNow] = useState(null);
  const [startTime, setStartTime] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let seconds = 0;
  if (startTime != null && now != null) {
    seconds = (now - startTime) / 1000;
  }

  return (
    <>
      <p>{seconds.toFixed(3)} 秒</p>
      <button onClick={handleStart}>开始</button>
      <button onClick={handleStop}>停止</button>
    </>
  );
}

startTimenow 用 state(需要渲染),intervalRef 用 ref(不需要渲染)。

本节要点

  • ref 是不触发渲染的可变值
  • 用于存储定时器 ID、DOM 引用、上一次的值
  • 渲染期间不读不写 ref
  • 需要显示的用 state,不需要显示的用 ref