返回首页

说说对受控组件和非受控组件的理解?应用场景?

问题解析(面试官考察点)

面试官通过此问题主要考察:

  • 对 React 表单处理机制的理解
  • 对数据流向的掌握
  • 能否根据场景选择合适的组件类型
  • 对 ref 的使用理解

核心概念(基础知识点)

受控组件(Controlled Component)

受控组件是指其值由 React 的 state 控制的表单元素:

  • 表单数据由 React 组件管理
  • 通过 value 属性设置值
  • 通过 onChange 事件更新值
  • 数据流:state -> input -> onChange -> setState -> state

非受控组件(Uncontrolled Component)

非受控组件是指其值由 DOM 自身管理的表单元素:

  • 表单数据由 DOM 节点管理
  • 使用 ref 获取值
  • 类似传统的 HTML 表单
  • 可以使用 defaultValue 设置初始值

详细解答(代码示例)

受控组件示例

class TestComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { username: 'lindaidai' };
  }

  handleChange = (e) => {
    this.setState({ username: e.target.value });
  };

  render() {
    return (
      <input
        name="username"
        value={this.state.username}
        onChange={this.handleChange}
      />
    );
  }
}

关键点:

  • 如果不设置 onChange,输入框将是只读的
  • 每次输入都会触发 state 更新
  • 组件重新渲染显示新值

完整的受控表单示例

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: '',
      email: '',
      gender: 'male'
    };
  }

  handleInputChange = (e) => {
    const { name, value } = e.target;
    this.setState({ [name]: value });
  };

  handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交的数据:', this.state);
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          用户名:
          <input
            type="text"
            name="username"
            value={this.state.username}
            onChange={this.handleInputChange}
          />
        </label>
        <br />
        <label>
          邮箱:
          <input
            type="email"
            name="email"
            value={this.state.email}
            onChange={this.handleInputChange}
          />
        </label>
        <br />
        <label>
          性别:
          <select
            name="gender"
            value={this.state.gender}
            onChange={this.handleInputChange}
          >
            <option value="male"></option>
            <option value="female"></option>
          </select>
        </label>
        <br />
        <button type="submit">提交</button>
      </form>
    );
  }
}

非受控组件示例

import React, { Component } from 'react';

export class UnControlled extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  handleSubmit = (e) => {
    e.preventDefault();
    console.log('我们可以获得 input 内的值为:', this.inputRef.current.value);
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input defaultValue="lindaidai" ref={this.inputRef} />
        <input type="submit" value="提交" />
      </form>
    );
  }
}

函数组件中的非受控组件

import React, { useRef } from 'react';

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

  const handleSubmit = (e) => {
    e.preventDefault();
    alert('输入的值: ' + inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={inputRef} defaultValue="默认值" />
      <button type="submit">提交</button>
    </form>
  );
}

深入理解(原理剖析)

受控组件的工作原理

用户输入
    ↓
onChange 事件触发
    ↓
setState 更新 state
    ↓
组件重新渲染
    ↓
input value 更新为新值

优势:

  1. 数据可控:所有数据都在 React 的 state 中
  2. 即时验证:可以在 onChange 中实时验证
  3. 条件禁用:可以根据条件禁用按钮
  4. 强制格式:可以强制特定输入格式

非受控组件的工作原理

用户输入
    ↓
DOM 直接更新
    ↓
提交时通过 ref 获取值

优势:

  1. 代码量少:不需要写 onChange 和 state
  2. 性能稍好:不需要频繁重新渲染
  3. 文件上传:文件输入必须是受控组件
  4. 集成非 React 代码:更容易与外部库集成

对比表

特性 受控组件 非受控组件
数据来源 React state DOM
值获取 this.state.xxx ref.current.value
值设置 value 属性 defaultValue 属性
实时验证 支持 需要额外处理
代码量 较多 较少
适用场景 复杂表单、需要验证 简单表单、快速实现

最佳实践

推荐使用受控组件的场景

  1. 需要即时验证
class ValidatedInput extends React.Component {
  state = { value: '', error: '' };

  handleChange = (e) => {
    const value = e.target.value;
    let error = '';

    if (value.length < 3) {
      error = '至少需要3个字符';
    }

    this.setState({ value, error });
  };

  render() {
    return (
      <div>
        <input
          value={this.state.value}
          onChange={this.handleChange}
        />
        {this.state.error && <span>{this.state.error}</span>}
      </div>
    );
  }
}
  1. 条件提交按钮
<button disabled={!this.state.isValid}>
  提交
</button>
  1. 动态输入格式
// 格式化电话号码
handlePhoneChange = (e) => {
  const formatted = this.formatPhone(e.target.value);
  this.setState({ phone: formatted });
};

推荐使用非受控组件的场景

  1. 简单的搜索框
function SearchForm() {
  const inputRef = useRef();

  const handleSearch = () => {
    const query = inputRef.current.value;
    // 执行搜索
  };

  return (
    <div>
      <input ref={inputRef} placeholder="搜索..." />
      <button onClick={handleSearch}>搜索</button>
    </div>
  );
}
  1. 文件上传
function FileUpload() {
  const fileInput = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    const file = fileInput.current.files[0];
    // 处理文件上传
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="file" ref={fileInput} />
      <button type="submit">上传</button>
    </form>
  );
}

混合使用示例

class MixedForm extends React.Component {
  state = {
    username: '',
    email: ''
  };
  fileInput = React.createRef();

  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  handleSubmit = (e) => {
    e.preventDefault();
    const formData = {
      ...this.state,
      file: this.fileInput.current.files[0]
    };
    console.log(formData);
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          name="username"
          value={this.state.username}
          onChange={this.handleChange}
        />
        <input
          name="email"
          value={this.state.email}
          onChange={this.handleChange}
        />
        <input type="file" ref={this.fileInput} />
        <button type="submit">提交</button>
      </form>
    );
  }
}

面试要点

  1. 核心区别

    • 受控:React state 管理值
    • 非受控:DOM 管理值,通过 ref 获取
  2. 选择依据

    • 需要即时反馈 -> 受控
    • 简单快速实现 -> 非受控
    • 文件上传 -> 非受控
    • 复杂表单验证 -> 受控
  3. 常见误区

    • 受控组件必须设置 onChange,否则无法输入
    • 非受控组件使用 defaultValue,不是 value
    • 不要混用受控和非受控模式
  4. 性能考虑

    • 受控组件每次输入都会触发重新渲染
    • 大型表单可以考虑使用 Formik、React Hook Form 等库
  5. 现代方案

    • React Hook Form:基于非受控组件的高性能表单方案
    • Formik:完整的表单解决方案
    • 推荐使用这些库处理复杂表单
  6. 常见面试题

    • 什么是受控组件和非受控组件?
    • 什么时候使用受控组件?
    • 文件上传为什么要用非受控组件?
    • 如何在函数组件中实现非受控组件?