0%

已完成 0/29 关

教学模式

你可以随时调整。

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

第 28 关 含代码 约 12 分钟

使用 Ref 操作 DOM:节点聚焦、滚动与测量

使用 Ref 操作 DOM:节点聚焦、滚动与测量

由于 React 会根据组件的 JSX 自动更新浏览器 DOM,因此在绝大部分时间里,你根本不需要亲自去操作真实的 DOM 元素。

但是,Web 平台有一些特定的能力,是无法仅靠声明式属性来完成的。例如:

  • 让一个特定的文本输入框立即获得焦点(Focus);
  • 平滑滚动网页到某个特定元素所在的位置(Scroll into view);
  • 测量某个元素在屏幕上的真实物理像素宽度或高度。

这些操作都需要你直接触摸到浏览器原生的 DOM 节点。React 允许你使用 useRef 来获得指向真实 DOM 节点的引用。


1. 将 Ref 附加到 DOM 节点

要获取一个原生 DOM 节点的引用,只需三步:

  1. 声明一个初始值为 null 的 ref:
    const inputRef = useRef(null);
  2. 将这个 ref 作为 ref 属性传递给你想要操作的原生 HTML 标签:
    <input ref={inputRef} />
  3. 当 React 把这个标签真正渲染到浏览器 DOM 中后,它就会把真实的 DOM 节点对象赋值给 inputRef.current!

2. 经典案例:点击按钮自动聚焦输入框

来看一个最经典的例子:点击“点击搜索”按钮,页面立刻自动把光标聚焦到输入框中,并调起键盘:

import { useRef } from 'react';

export default function Form() {
  // 1. 声明 ref
  const inputRef = useRef(null);

  function handleClick() {
    // 3. 直接调用原生浏览器 DOM 节点的 focus() 方法!
    inputRef.current.focus();
  }

  return (
    <>
      {/* 2. 绑定到 input 标签 */}
      <input ref={inputRef} placeholder="请输入关键词..." />
      <button onClick={handleClick}>
        点击开始搜索
      </button>
    </>
  );
}

用户点击按钮,触发 handleClick,代码安全地调用了原生的 inputRef.current.focus(),光标瞬间进入输入框!


3. 经典案例:平滑滚动到指定元素(Scroll into view)

假设你有一个包含三张猫咪大图的相册列表,顶部有三个快捷按钮。点击任意一个按钮,页面丝滑地滚动到对应的猫咪大图:

import { useRef } from 'react';

export default function CatFriends() {
  const firstCatRef = useRef(null);
  const secondCatRef = useRef(null);
  const thirdCatRef = useRef(null);

  function handleScrollTo(ref) {
    ref.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    <>
      <nav>
        <button onClick={() => handleScrollTo(firstCatRef)}>波斯猫</button>
        <button onClick={() => handleScrollTo(secondCatRef)}>布偶猫</button>
        <button onClick={() => handleScrollTo(thirdCatRef)}>暹罗猫</button>
      </nav>
      <div>
        <ul>
          <li>
            <img
              src="https://placecats.com/neo/300/200"
              alt="波斯猫"
              ref={firstCatRef}
            />
          </li>
          <li>
            <img
              src="https://placecats.com/millie/200/200"
              alt="布偶猫"
              ref={secondCatRef}
            />
          </li>
          <li>
            <img
              src="https://placecats.com/bella/199/200"
              alt="暹罗猫"
              ref={thirdCatRef}
            />
          </li>
        </ul>
      </div>
    </>
  );
}

4. React 究竟在什么时候为 ref 赋值?

理解 React 为 ref 赋值的时机非常关键:

  • 在渲染阶段(Render phase):DOM 节点此时还根本没有被创建,所以此时读取 inputRef.current,它的值必然是 null;
  • 在提交阶段(Commit phase):React 把计算出的真实 DOM 挂载到浏览器屏幕上,并在此刻立即将 DOM 节点赋给 inputRef.current;
  • 在卸载阶段(Unmount phase):当节点从屏幕上被移除时,React 会重新把 inputRef.current 置为 null,防止内存泄漏。

5. 危险警告:避免用原生方法修改由 React 管理的 DOM!

一旦你拿到了真实 DOM 节点,浏览器允许你做任何事情,比如 node.remove() 或者 node.appendChild()。

严正警告:千万不要用原生 DOM API 手动增删、清空或篡改由 React 管理的子节点! 如果你用 inputRef.current.remove() 把节点删除了,而 React 还以为它在,当下一次重新渲染比对时,React 就会找不到该节点从而引发致命崩溃。

安全边界总结:

  • 安全操作(完全无害):聚焦(focus())、失去焦点(blur())、滚动(scrollIntoView())、测量尺寸(getBoundingClientRect());
  • 危险操作(切勿触碰):直接修改 DOM 的子元素结构(appendChild、innerHTML、removeChild)。结构的变化应该始终交给 React 的 State 和 JSX 来驱动。

总结

  • 使用 <tag ref={myRef}> 让 React 把真实 DOM 节点注入给 myRef.current;
  • DOM ref 是进行聚焦、视口滚动和物理尺寸测量的标准脱围通道;
  • React 在 DOM 提交完成后才赋值 ref,在卸载时赋为 null;
  • 严禁使用原生 DOM 方法直接篡改由 React 管理的 DOM 树结构。
下一站:使用 Effect 与外部系统同步:渲染后取数据与清理机制 →
© 2026 React 官方教程