Kodokon kodokon.com

useRef 与 DOM 操作

用 useRef 操作 DOM 并保存实例值,同时不破坏渲染的纯粹性。

7 分钟 · 3 题

在 Kodokon 中打开本课

useRef 返回一个可变的盒子——一个只有 current 属性的对象——它的标识在每次渲染中都保持不变。它与 state 有两个根本区别:修改 ref.current 不会触发任何重新渲染,而且改动立即可见,无需等到下一次渲染。由此衍生出两类用途:引用一个 DOM 节点,以及存储一个必须在多次渲染中存活、却又不驱动显示的实例值。

JSX
import { useEffect, useRef } from "react";

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

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} type="search" />;
}
React 在渲染被提交之后才把节点附加到 ref 上。

在实例值这一面,任何必须存留却不引发渲染的东西都该放进 ref:setInterval 返回的 id、某个外部库的实例(一张地图、一个编辑器、一个视频播放器),或者上一次渲染留下的值。金科玉律始终不变:渲染必须是 props 和 state 的纯函数。ref 在 effect 和事件处理函数里被读写,而不是在渲染期间。

JAVASCRIPT
import { useEffect, useRef } from "react";

function usePrevious(value) {
  const ref = useRef(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}
effect 在渲染之后运行:ref 始终落后一次渲染。

要从你自己的组件里暴露一个 DOM 节点,把 ref 往下传:自 React 19 起,ref 就是函数组件的一个普通 prop;在那之前,必须经过 forwardRef。最后要养成一个习惯:当 state 足以描述结果时(一个 CSS class、一个 disabled 属性),优先采用声明式的方式。把 ref 留给 React 覆盖不到的领域:聚焦、文本选择、测量元素、滚动、媒体播放。

JSX
import { useRef } from "react";

function TextField({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

function Form() {
  const fieldRef = useRef(null);

  return (
    <>
      <TextField ref={fieldRef} />
      <button onClick={() => fieldRef.current.focus()}>
        Give focus
      </button>
    </>
  );
}
自 React 19 起,ref 像任何其他 prop 一样被传递。

知识检测

确认你已牢记本课的重点内容。

  1. 在存储一个值时,useRef 与 useState 有什么根本区别?
    • 修改 ref.current 不会触发任何重新渲染,这与 state 更新不同
    • useRef 在组件卸载之后仍然保留它的值
    • useRef 只能存储 DOM 节点
    • ref 在一个组件的所有实例之间共享
  2. 为什么要在 useEffect 里而不是在渲染期间访问 inputRef.current?
    • 因为 useEffect 会批处理 DOM 访问以获得更好的性能
    • 因为 ref 在渲染期间被锁定为只读
    • 因为 React 只在提交之后才附加节点:在首次渲染期间,current 仍然是 null
  3. usePrevious(value) 在最开始的第一次渲染时返回什么?
    • value 的当前值
    • undefined,因为写入 ref 的那个 effect 还没运行
    • null
    • 下一次渲染的值