首页 / React 19 入门教程 / useRef 操作 DOM

React 19 入门教程

useRef 操作 DOM

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

ReactuseRefDOM操作ref回调forwardRef

31. useRef 操作 DOM

本节目标:掌握用 ref 访问 DOM 节点,实现聚焦、滚动、测量等操作,了解 ref 回调和 forwardRef。

获取 DOM 节点的 ref

React 自动管理 DOM,但在需要时你可以”插手”。三个步骤:

import { useRef } from 'react';

function Form() {
  // 1. 创建 ref
  const inputRef = useRef(null);

  function handleClick() {
    // 3. 通过 ref.current 访问 DOM
    inputRef.current.focus();
  }

  return (
    <>
      {/* 2. 绑定到 JSX */}
      <input ref={inputRef} />
      <button onClick={handleClick}>聚焦输入框</button>
    </>
  );
}

React 挂载组件后,会把 DOM 节点赋值给 ref.current。卸载时重置为 null

ref.current 初始值是 null

第一次渲染时 ref.currentnull,因为 DOM 还没创建。在 effect 或事件处理函数里才能安全访问。

聚焦和选择文本

function SearchBox() {
  const inputRef = useRef(null);

  function handleFocus() {
    inputRef.current.focus();
  }

  function handleSelectAll() {
    inputRef.current.select(); // 选中全部文本
  }

  return (
    <>
      <input ref={inputRef} defaultValue="搜索内容" />
      <button onClick={handleFocus}>聚焦</button>
      <button onClick={handleSelectAll}>全选</button>
    </>
  );
}

滚动到指定元素

function ScrollDemo() {
  const targetRef = useRef(null);

  function handleScroll() {
    targetRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'center'
    });
  }

  return (
    <>
      <button onClick={handleScroll}>滚到底部</button>
      <div style={{ height: '1000px' }}>占位</div>
      <div ref={targetRef}>目标位置</div>
    </>
  );
}

测量 DOM 尺寸

function MeasureDemo() {
  const boxRef = useRef(null);
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const { width, height } = boxRef.current.getBoundingClientRect();
    setSize({ width, height });
  }, []);

  return (
    <>
      <div ref={boxRef} style={{ width: '50%', background: '#eee' }}>
        我是被测量的盒子
      </div>
      <p>尺寸:{size.width.toFixed(0)} x {size.height.toFixed(0)}</p>
    </>
  );
}

ref 回调

ref 可以是一个函数,React 会在挂载和卸载时调用它:

function MeasureList() {
  const [heights, setHeights] = useState([]);

  function measureRef(node) {
    if (node) {
      // 挂载时
      console.log('新节点高度:', node.getBoundingClientRect().height);
    } else {
      // 卸载时
      console.log('节点被移除');
    }
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} ref={measureRef}>
          {item.text}
        </li>
      ))}
    </ul>
  );
}

ref 回调适合列表场景

列表数量不固定时,ref 回调比 useRef 数组更方便。

访问子组件的 DOM:forwardRef

默认情况下,ref 只能指向 DOM 元素,不能指向组件。forwardRef 让组件可以转发 ref 到内部的 DOM:

import { forwardRef, useRef } from 'react';

const FancyInput = forwardRef(function FancyInput(props, ref) {
  return <input ref={ref} {...props} className="fancy" />;
});

function Parent() {
  const ref = useRef(null);

  function handleFocus() {
    ref.current.focus(); // 直接聚焦子组件的 input
  }

  return (
    <>
      <FancyInput ref={ref} />
      <button onClick={handleFocus}>聚焦</button>
    </>
  );
}

React 19 中 ref 可以作为普通 prop

React 19 开始,函数组件可以直接接收 ref 作为 prop,不需要 forwardRef。但 forwardRef 仍然兼容。

多个 ref

一个组件可以有多个 ref:

function MultiRefForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const submitRef = useRef(null);

  function handleStart() {
    nameRef.current.focus();
  }

  return (
    <>
      <input ref={nameRef} placeholder="姓名" />
      <input ref={emailRef} placeholder="邮箱" />
      <button ref={submitRef} onClick={handleStart}>提交</button>
    </>
  );
}

操作 DOM 的安全原则

  1. 只做 React 不擅长的事:聚焦、滚动、测量。其他交给 React。
  2. 不要修改 React 管理的 DOM:React 可能覆盖你的修改。
  3. 在 effect 里操作:确保 DOM 已经存在。
  4. 清理副作用:在 effect 返回的清理函数里撤销。

不要绕过 React 更新 DOM

用 ref 直接修改 innerHTMLtextContent 等,React 不知道这些变化,下次渲染会覆盖。

本节要点

  • ref 属性绑定 DOM 节点到 useRef
  • ref.current.focus() 让元素获得焦点
  • scrollIntoView() 平滑滚动
  • getBoundingClientRect() 测量尺寸
  • forwardRef 让子组件暴露内部 DOM 给父组件