9.受控组件和非受控组件有什么区别?应该在什么场景下选择使用它们? 副本

查看飞书原文 ↗3,586 字符
在 React 开发中,处理表单元素(如 <input>、<textarea> 和 <select>)时,我们总会遇到两种模式:**受控组件(Controlled Components)** 和 **非受控组件(Uncontrolled Components)**。这两种模式的核心区别在于数据(state)由谁来管理。​
​
理解它们的差异与适用场景,能帮助我们构建出更可预测、更易维护的应用。​
​
什么是受控组件?​
​
在受控组件中,表单元素的值由 React 的 state **完全控制**。我们可以将其理解为,React state 是表单数据的“唯一数据源”(Single Source of Truth)。​
​
它的工作流程通常如下:​
​
1.
组件的 state 中存储着表单元素的当前值。​
2.
这个 state 值通过 value (或 checked for checkboxes/radios) prop 传递给表单元素。​
3.
当用户与表单元素交互时(例如输入文字),会触发 onChange 事件。​
4.
onChange 事件处理器会更新组件的 state。​
5.
state 的更新导致组件重新渲染,表单元素也随之显示出新的值。​
​
示例:一个受控的输入框​
​
import React, { useState } from 'react';

function ControlledForm() {
  const [name, setName] = useState('');

  const handleChange = (event) => {
    // 实时将输入值更新到 state
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    alert('提交的名字是: ' + name);
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        名字:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <button type="submit">提交</button>
    </form>
  );
}
​
在上述代码中,<input> 的值完全由 name 这个 state 驱动。用户的任何输入都必须经过 handleChange -> setName -> state 更新 -> 重新渲染 这个循环,才能在界面上生效。​
​
受控组件的优势​
​
•
即时验证:由于每次输入都会更新 state,我们可以在 handleChange 中轻松实现实时的数据校验、格式化或字符限制。​
•
条件化逻辑:可以根据输入框的值,动态地禁用提交按钮或显示提示信息。​
•
单一数据源:表单状态集中在组件的 state 中管理,使得调试和状态追踪变得更加清晰、可预测。​
​
​
​
​
什么是非受控组件?​
​
与受控组件相反,非受控组件的表单数据由 **DOM 自身**管理,而不是 React state。React 不再直接控制表单元素的值,而是需要时才从 DOM 中“拉取”它。​
​
我们通常使用 ref 来获取对 DOM 节点的直接引用,从而在需要时(例如表单提交时)读取其当前值。​
​
示例:一个非受控的输入框​
​
import React, { useRef } from 'react';

function UncontrolledForm() {
  const nameInputRef = useRef(null);

  const handleSubmit = (event) => {
    // 通过 ref 从 DOM 中直接获取值
    alert('提交的名字是: ' + nameInputRef.current.value);
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        名字:
        {/* 使用 defaultValue 设置初始值,且没有 value prop */}
        <input type="text" defaultValue="Guest" ref={nameInputRef} />
      </label>
      <button type="submit">提交</button>
    </form>
  );
}
​
在这里,我们使用 useRef 创建了一个 ref 并附加到 <input> 元素上。请注意,我们使用的是 defaultValue 而不是 value 来设置初始值。React 不会追踪输入框值的变化,只有当 handleSubmit 被调用时,我们才通过 nameInputRef.current.value 去读取 DOM 中的最新值。​
​
非受控组件的优势​
​
•
代码更简洁:对于简单的表单,无需为每个输入框都编写 onChange 处理器和 useState,代码量更少。​
•
更接近原生 HTML:其工作方式与传统 HTML 表单类似,学习成本较低。​
•
集成方便:在与一些非 React 的、直接操作 DOM 的库集成时可能更方便。​
•
性能:在极少数情况下,如果一个表单包含大量输入元素且实时更新 state 导致性能问题,非受控组件可以避免每次输入都触发重新渲染。​
​
​
​
​
对比与选择指南​
​
为了更直观地对比,我们可以从以下几个维度来分析:​
​
​
​
特性​
​
​
受控组件 (Controlled)​
​
​
非受控组件 (Uncontrolled)​
​
​
数据流​
​
​
React state → UI (单向数据流)​
​
​
DOM 自身管理 state​
​
​
值来源​
​
​
React state 是唯一的数据源​
​
​
DOM 是数据源,通过 ref 读取​
​
​
实现方式​
​
​
value + onChange + useState​
​
​
ref + defaultValue​
​
​
实时交互​
​
​
强大,可轻松实现即时验证、动态 UI​
​
​
弱,需要在特定事件(如 submit)中手动获取​
​
​
复杂度​
​
​
相对更高,需要更多模板代码​
​
​
更低,代码简洁​
​
​
​
我们应该如何选择?​
​
首选受控组件 是 React 社区普遍推荐的最佳实践。它更符合 React 声明式编程的理念,通过 state 驱动 UI,让组件的行为更加可预测和易于控制。​
​
在以下场景中,受控组件是理想之选:​
​
•
需要对用户输入进行**实时验证**或格式化。​
•
需要根据一个输入框的值,**动态地改变**另一个 UI 元素(例如,当输入框为空时禁用提交按钮)。​
•
多个表单字段之间存在依赖关系。​
​
在以下特定场景中,可以考虑使用非受控组件:​
​
•
非常简单的表单:例如一个搜索框或登录表单,我们只关心提交时的最终值。​
•
一次性取值:当你只想在特定时刻读取一次值,而不需要追踪它的每一次变化时。​
•
处理文件输入:<input type="file" /> 的 value 是只读的,因此它本质上就是一个非受控组件。我们必须通过 ref 来访问其选择的文件。​
•
性能瓶颈:在一个包含成百上千个输入元素的极其复杂的表单中,如果受控组件的频繁 state 更新确实引发了性能问题(请务必先进行性能分析),非受控组件可能是一个优化手段。​
​
总结​
​
受控组件和非受控组件是 React 提供的两种处理表单的有效模式,它们没有绝对的优劣之分,而是服务于不同的需求。​
​
•
受控组件将表单数据纳入 React 的 state 管理体系,提供了强大的控制力和可预测性,是大多数情况下的**首选方案**。​
•
非受控组件则将数据管理权交还给 DOM,实现起来更简单快捷,是在一些简单场景或特定需求下的**实用备选**。​
​
理解这两种模式的内在机制和各自的权衡,能帮助我们在开发中做出更明智的技术决策,编写出既健壮又易于维护的 React 代码。​
​