跳到主要内容

使用 ref 操作 DOM

如何拿到 React 管理的 DOM 节点?​

  • 绑定节点: 将 ref 对象传给标签的 ref 属性,React 在提交阶段关联节点,在清理时解除关联;
  • 调用时机: 事件处理器或合适的 Effect 中调用 focus、scrollIntoView 等方法,渲染时节点可能尚未存在;
import { useRef } from "react";

function Search() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>聚焦</button>
</>
);
}

如何为动态列表保存多个 DOM 节点?​

  • 回调 ref: 为每项提供接收节点的函数,在提交时记录到 Map,返回清理函数在解除关联时删除;
  • Hooks 限制: 不在 map 循环里调用 useRef,改用一个 Map 容器或提取列表项组件;
  • 回调身份: 每次创建不同 ref 回调会使 React 清理旧回调再设置新回调,清理必须与注册对称;
const nodes = useRef(new Map());
const list = items.map((item) => (
<li
key={item.id}
ref={(node) => {
nodes.current.set(item.id, node);
return () => {
nodes.current.delete(item.id);
};
}}
>
{item.name}
</li>
));

如何访问子组件内部的 DOM?​

  • Ref 输入: React 19 函数组件可接收 ref prop,再传给内部标签,不需要展开旧转发 API;
  • 能力限制: 不希望暴露整个 DOM 时用 useImperativeHandle,只提供允许的操作,见 Context 与 Ref 参考;
function MyInput({ ref, ...props }) {
return <input {...props} ref={ref} />;
}

更新 state 后立刻操作新节点时需要注意什么?​

  • 提交延迟: setter 之后 DOM 可能仍对应旧状态,立即滚动可能滚到旧条目;
  • 同步桥接: 必须在当前回调返回前看到新 DOM 的浏览器集成才考虑 flushSync,契约见 ReactDOM 概览;
  • 所有权: 焦点、滚动和测量通常安全,不要手动删除或修改 React 正在管理的子节点,避免双方状态失配;