useRef 深度解析:DOM 引用与受控、非受控表单的取舍

39 阅读3分钟

useRef 深度解析:DOM 引用与受控、非受控表单的取舍

在 React Hooks 中,useRef 常被低估。它不像 useState 那样直接驱动渲染,却在许多场景中不可或缺。本文聚焦 useRef 的本质,结合表单控制,清晰对比受控与非受控组件,帮助你明白“什么时候用 useRef,为什么这样选”。

1. useRef 的本质
const ref = useRef(initialValue);

useRef 返回一个可变对象 { current: initialValue },具备三个关键特性:

  • 可直接修改 .current
  • 修改不触发组件渲染
  • 对象在组件整个生命周期保持不变

核心结论:useRef 不是用来驱动 UI,而是用来“持久存储”数据的容器。

2. useRef vs useState:根本区别
对比点useStateuseRef
修改是否触发渲染
是否响应式
适合驱动 UI
适合存储中间状态否(会引发不必要渲染)

简言之:useState 管理“状态”(影响视图),useRef 管理“存储”(不影响视图)。

3. useRef 典型场景

(1)直接访问 DOM 元素

const inputRef = useRef(null);

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

适用于:自动聚焦、滚动定位、测量尺寸等命令式操作。

(2)存储不影响渲染的可变值

const timerId = useRef(null);

const start = () => {
  timerId.current = setInterval(() => {}, 1000);
};

const stop = () => {
  clearInterval(timerId.current);
};

若用 useState 存储 timerId,会导致无意义渲染。useRef 更清晰、更高效。

4. 表单控制:受控组件 vs 非受控组件

(1)受控组件

const [value, setValue] = useState('');

<input value={value} onChange={e => setValue(e.target.value)} />;
  • 值完全由 React state 控制
  • 每次输入都触发渲染

优势

  • 实时校验、格式化、联动
  • 状态可预测、易调试

代价

  • 频繁渲染,字段多时性能消耗增大

适合:复杂表单(如注册、订单填写)需要实时控制的场景。

(2)非受控组件

const inputRef = useRef(null);

// 提交时读取
const value = inputRef.current.value;
<input ref={inputRef} />;
  • 值由 DOM 自身维护
  • React 只在需要时读取

优势

  • 无频繁渲染,性能更好
  • 代码简洁

局限

  • 无法实时校验或联动
  • 状态不可预测

适合:简单一次性提交场景(如搜索框、评论提交、文件上传)。

(3)受控 vs 非受控 快速对比

维度受控组件非受控组件
值来源React stateDOM 自身
是否实时响应
性能一般更好
适合场景复杂交互、实时校验简单提交、一次性读取

选择原则
需要实时“控制”输入过程 → 受控组件
只需在提交时“获取”结果 → 非受控组件(借助 useRef)

5. useRef 在表单体系中的定位

useRef 不是 state 的替代品,而是:

  • 提供命令式 DOM 操作的官方入口
  • 为非受控组件实现“按需读取”
  • 存储不参与渲染的中间数据

实际项目中,受控与非受控往往混合使用,useRef 起到桥梁作用。

6. 总结
  • useRef 是不会触发渲染的可变容器,专用于存储 DOM 引用、定时器、中间变量等。
  • 受控组件以状态驱动 UI,适合复杂实时交互;非受控组件让 DOM 自治,适合简单高效提交。
  • 核心决策点只有一个:这个值是否需要驱动视图渲染?
    需要 → 用 useState(受控)
    不需要 → 用 useRef(非受控或存储)