面试官:说说 React 中受控组件与非受控组件的区别?

9 阅读5分钟

面试官:说说 React 中受控组件与非受控组件的区别?

在 React 开发中,处理表单元素是家常便饭。而在面试中,关于“受控组件(Controlled Components)”与“非受控组件(Uncontrolled Components)”的区别,往往是考察候选人对 React 数据流理解深度的经典问题。

简单来说,这二者的核心区别在于:“谁拥有数据的决定权?”

1. 受控组件:React 是“唯一的真理之源”

概念解析: 受控组件意味着表单元素的值(value)完全由 React 的 state(状态)来控制。DOM 元素本身并不维护状态,它只是 React 状态的一个“镜像”。用户输入时,必须通过 onChange 事件回调来更新状态,从而实现数据的双向绑定。

面试话术: “受控组件遵循单向数据流原则。React 拥有数据的最高控制权,输入框的显示值完全取决于 state。这种模式让数据处理非常直观,因为所有的变更都在 React 的掌控之中。”

代码示例分析: 以下是一个典型的受控组件登录表单。value 被绑定到了 form 状态,任何输入都会触发 handleChange 来同步更新状态。

import { useState } from 'react';

export default function LoginForm() {
    //  状态完全由 React 控制
    const [form, setForm] = useState({
        username: '',
        password: ''
    });

    const handleChange = (e) => {
        //  每次输入都实时更新 React 状态
        setForm({
            ...form,
            [e.target.name]: e.target.value
        })
    }

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log(form); //  提交时直接获取最新的 React 状态
    }

  return (
    <form onSubmit={handleSubmit}>
        <input 
        type="text"
        name="username"
        //  值受 state 控制onChange  React 监听
        value={form.username}
        onChange={handleChange}
        />
        <input 
        type="password"
        name="password"
        value={form.password}
        onChange={handleChange}
        />
        <button type="submit">注册</button>
    </form>
  )
}

2. 非受控组件:DOM 是“真理之源”

概念解析: 非受控组件是指表单元素的值由 DOM 自身来维护,而不是通过 React 的 state。React 仅在需要的时候(例如表单提交时)通过 ref 去“查询” DOM 获取值,而不是实时监听。

面试话术: “非受控组件更像是传统的 HTML 表单。它的值由原生 DOM 管理,React 仅作为‘旁观者’。我们通常使用 useRef 来直接引用 DOM 节点,从而在特定时刻读取其值。”

代码示例分析: 在这个评论框组件中,我们没有使用 onChange 来监听每一次键盘敲击,而是在点击提交按钮时,直接通过 textareaRef.current.value 从 DOM 中取出值。

import { useRef } from "react";

export default function CommentBox() {
    //  创建一个 ref 来直接引用 DOM 节点
    const textareaRef = useRef(null);

    const handleSubmit = () => {
        //  直接从 DOM 中获取值,而不是从 state 中
        const comment = textareaRef.current.value;
        if(!comment) return alert('请输入评论');
        console.log(comment);
    }

  return (
    <div>
        {/*  没有 value 和 onChange,仅通过 ref 绑定 */}
        <textarea 
          ref={textareaRef}
          placeholder="输入评论..."
        ></textarea>
        <button onClick={handleSubmit}>提交</button>
    </div>
  )
}

3. 深度对比:如何在面试中展示你的技术选型能力?

当面试官问“什么时候用哪个”时,你需要展示出对业务场景的权衡能力。

维度受控组件非受控组件
数据流向单向数据流 (State -> View -> State)直接操作 DOM
性能频繁触发 setState 可能导致重渲染,开销较大仅在需要时读取,性能更优
代码复杂度逻辑清晰,但代码量稍多(需写 handler)代码简洁,接近原生 JS 逻辑
适用场景实时验证、动态表单、数据联动、复杂交互文件上传、简单的搜索框、集成非 React 库

4. 总结

在面试的最后,你可以这样总结来拔高回答:

  • 受控组件是 React 的“首选模式”。它将数据和 UI 同步,便于实现即时反馈(如输入限制、表单验证),非常适合需要高交互性的复杂表单系统。
  • 非受控组件则是一种“性能妥协”或“简单场景下的快捷方式”。当表单仅用于一次性提交,或者为了优化大量输入框的性能时,使用 ref 直接操作 DOM 是一个非常有效的策略。

掌握这两者的区别,不仅能帮你写出更高效的代码,还能让你在面对不同业务需求时做出更合理的技术选型。


5. 关键补充:onChange 事件在双向绑定中的作用

在讨论受控组件时,onChange 事件是实现“双向绑定”幻觉的关键一环。理解它的行为对于避免常见的陷阱至关重要。

onChange 的工作原理: 在 React 中,onChange 是一个合成事件(SyntheticEvent),它是对原生 DOM 事件的跨浏览器包装。对于 <input><textarea> 等元素,React 的 onChange 事件行为更接近于原生的 input 事件,即在值每次改变时立即触发,而不是像原生 change 事件那样需要等到元素失去焦点时才触发。

为什么 onChange 不可或缺? 在受控组件中,如果你只设置了 value 属性而没有提供 onChange 处理函数,输入框将会变成只读的。这是因为 value 属性将输入框的值锁定到了 React 的状态,而缺少 onChange 意味着没有机制来更新这个状态。用户输入时,状态不会改变,React 重新渲染时输入框的值依然保持原样,从而表现为无法输入。

因此,onChange 是连接用户输入(视图层)和 React 状态(数据层)的桥梁,它确保了数据的双向流动。

onChange 事件对象(e)的使用: onChange 处理函数会接收一个事件对象 e。通过 e.target,你可以访问到触发事件的 DOM 元素,并从中获取最新的值。

  • 对于文本输入框(<input type="text">)、文本域(<textarea>)等,使用 e.target.value 获取用户输入的最新字符串。
  • 对于复选框(<input type="checkbox">)和单选按钮(<input type="radio">),使用 e.target.checked 获取其是否被选中的布尔值。
const handleChange = (e) => {
  // 对于文本输入
  const textValue = e.target.value; 
  // 对于复选框
  const isChecked = e.target.checked; 
  // ... 更新状态
};

正确理解和使用 onChange 事件,是构建响应式和用户友好型表单的基础。