Skip to content

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

React 开发中,处理表单元素(如 <input><textarea><select>)时,我们总会遇到两种模式:受控组件(Controlled Components非受控组件(Uncontrolled Components。这两种模式的核心区别在于数据(state)由谁来管理。

理解它们的差异与适用场景,能帮助我们构建出更可预测、更易维护的应用。

什么是受控组件?

在受控组件中,表单元素的值由 Reactstate 完全控制。我们可以将其理解为,React state 是表单数据的 唯一数据源(Single Source of Truth)

它的工作流程通常如下:

    1. 组件的 state 中存储着表单元素的当前值。
    1. 这个 state 值通过 value (或 checked for checkboxes/radios) prop 传递给表单元素。
    1. 当用户与表单元素交互时(例如输入文字),会触发 onChange 事件。
    1. onChange 事件处理器会更新组件的 state
    1. state 的更新导致组件重新渲染,表单元素也随之显示出新的值。

示例:一个受控的输入框

javascript
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 stateReact 不再直接控制表单元素的值,而是需要时才从 DOM 中“拉取”它。

我们通常使用 ref 来获取对 DOM 节点的直接引用,从而在需要时(例如表单提交时)读取其当前值。

示例:一个非受控的输入框

javascript
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 导致性能问题,非受控组件可以避免每次输入都触发重新渲染。

对比与选择指南

为了更直观地对比,我们可以从以下几个维度来分析:

特性受控组件非受控组件
数据流React state → UI (单向数据流)DOM 自身管理 state
值来源React state 是唯一的数据源DOM 是数据源,通过 ref 读取
实现方式value + onChange + useStateref + defaultValue
实时交互强大,可轻松实现即时验证、动态 UI弱,需要在特定事件(如 submit)中手动获取
复杂度相对更高,需要更多模板代码更低,代码简洁

我们应该如何选择?

首选受控组件React 社区普遍推荐的最佳实践。它更符合 React 声明式编程的理念,通过 state 驱动 UI,让组件的行为更加可预测和易于控制。

在以下场景中,受控组件是理想之选:

  • 需要对用户输入进行实时验证或格式化。

  • 需要根据一个输入框的值,动态地改变另一个 UI 元素(例如,当输入框为空时禁用提交按钮)。

  • 多个表单字段之间存在依赖关系。

在以下特定场景中,可以考虑使用非受控组件:

  • 非常简单的表单:例如一个搜索框或登录表单,我们只关心提交时的最终值。

  • 一次性取值:当你只想在特定时刻读取一次值,而不需要追踪它的每一次变化时。

  • 处理文件输入<input type="file" />value 是只读的,因此它本质上就是一个非受控组件。我们必须通过 ref 来访问其选择的文件。

  • 性能瓶颈:在一个包含成百上千个输入元素的极其复杂的表单中,如果受控组件的频繁 state 更新确实引发了性能问题(请务必先进行性能分析),非受控组件可能是一个优化手段。

总结

受控组件和非受控组件是 React 提供的两种处理表单的有效模式,它们没有绝对的优劣之分,而是服务于不同的需求。

  • 受控组件将表单数据纳入 Reactstate 管理体系,提供了强大的控制力和可预测性,是大多数情况下的首选方案

  • 非受控组件则将数据管理权交还给 DOM,实现起来更简单快捷,是在一些简单场景或特定需求下的实用备选

理解这两种模式的内在机制和各自的权衡,能帮助我们在开发中做出更明智的技术决策,编写出既健壮又易于维护的 React 代码。

不知道说啥了很无语了