0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 27 关 含代码 含表格 约 11 分钟

使用 Ref 引用值:在不重新渲染的情况下记住信息

使用 Ref 引用值:在不重新渲染的情况下记住信息

欢迎来到 React 的 “脱围机制(Escape Hatches)”。 在前面的学习中,你掌握了所有的标准声明式技术:组件、JSX、Props、State。大多数应用 99% 的界面逻辑都应该建立在这些核心之上。

但有时,你可能会遇到一些特殊的现实需求:你希望组件“记住”某些信息,但这些信息改变时,你绝不希望触发一次全新的重新渲染。

这时,你可以使用 React 提供的脱围工具之一:useRef Hook。


1. 为组件添加一个 Ref

使用 useRef,需要从 React 引入并传入初始值:

import { useRef } from 'react';

const ref = useRef(0);

useRef 会返回一个极其简单的普通 JavaScript 对象,它的形状长成这样:

{
  current: 0 // 你传入的初始值
}

你可以随时通过 ref.current 读取它,也可以随时向 ref.current 赋新值。

核心差异:

  • 当你修改一个 State 时,React 会重新渲染组件;
  • 当你修改一个 Ref 的 current 属性时,React 根本不会重新渲染组件!

你可以把 Ref 想象成组件的一个私密小口袋:你可以在里面存放任何东西,而且你在口袋里悄悄翻动东西时,绝对不会惊动屏幕上的任何像素。


2. Ref vs State:全方位对比

对比维度useRef(initialValue)useState(initialValue)
返回值返回 { current: initialValue } 可变对象返回 [value, setValue] 元组
修改方式直接赋值:ref.current = 5调用 Setter:setValue(5)
修改是否触发渲染不会触发任何重新渲染!会排队触发一次重新渲染!
可变性可变的(Mutable):可以直接在事件中读写不可变的(Immutable):必须使用新状态替换旧状态
在渲染期间是否可用绝不应该在渲染中读写 ref.current!可以随时在渲染中安全读取 state 快照

3. 经典实战:制作一个高精度秒表(Stopwatch)

秒表是说明 useRef 价值的最绝佳案例。 我们要制作一个秒表:点击“开始”,每隔 10 毫秒刷新一次时间;点击“停止”,计时器暂停。

为了驱动界面的时间跳动,我们需要 State:

  • startTime:开始时的时间戳;
  • now:当前的时间戳。

但是,要让定时器停止,我们需要调用浏览器的 clearInterval(intervalId)。 这个 intervalId 应该存在哪里?

  • 如果存在普通局部变量里,每次组件重新渲染时局部变量都会被销毁,停止按钮根本拿不到它;
  • 如果存在 useState 里,每次记下 intervalId 就会多余触发一次渲染,毫无必要;
  • 最好的选择是:保存在 useRef 中!
import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  // 用 ref 保存定时器的 ID,它的改变不需要触发任何渲染!
  const timerRef = useRef(null);

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

    // 启动定时器,每 10 毫秒更新一次当前时间
    clearInterval(timerRef.current);
    timerRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    // 停止定时器
    clearInterval(timerRef.current);
  }

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

  return (
    <>
      <h1>已走过时间: {secondsPassed.toFixed(3)} 秒</h1>
      <button onClick={handleStart}>
        开始计时
      </button>
      <button onClick={handleStop}>
        停止计时
      </button>
    </>
  );
}

4. 黄金铁律:不要在渲染过程中读写 ref.current!

React 期望你的组件主体(函数渲染过程)保持纯函数的纯净性。 如果你的组件在计算 JSX 的时候直接写:

// ❌ 严重违规!绝对不要在渲染计算中修改 ref!
function MyComponent() {
  const ref = useRef(0);
  ref.current = ref.current + 1; // 渲染时不纯!
  return <h1>{ref.current}</h1>; // 渲染中读取 ref 会导致不可预测的渲染差异
}

正确规则:

  • 只在事件处理函数(Event Handlers),或者稍后要学的 useEffect 中读写 ref.current!
  • 如果某个值需要直接用来决定 JSX 怎么渲染,请使用 useState。

总结

  • useRef 返回一个在重新渲染之间持久保留的可变对象 { current: initialValue };
  • 更改 ref.current 不会触发重新渲染;
  • 适合用来存储定时器 ID、DOM 元素引用或任何不直接参与 JSX 计算的幕后标识;
  • 绝不在渲染计算期间直接读写 ref.current。
下一站:使用 Ref 操作 DOM:节点聚焦、滚动与测量 →
© 2026 React 官方教程