Skip to content

受控与非受控组件深入

概述

表单是Web应用中最重要的用户交互方式之一。在React中,处理表单有两种主要方式:受控组件和非受控组件。理解这两种方式的原理、使用场景和最佳实践,是掌握React表单开发的基础。本文将深入探讨受控与非受控组件的所有细节。

受控组件

基础概念

受控组件是指表单元素的值由React的state控制,每次值改变都会触发state更新,state更新又会重新渲染组件。

jsx
import { useState } from 'react';

// 基础受控输入框
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <p>当前值: {value}</p>
    </div>
  );
}

// 受控文本域
function ControlledTextarea() {
  const [text, setText] = useState('');
  
  return (
    <textarea
      value={text}
      onChange={(e) => setText(e.target.value)}
      rows={5}
    />
  );
}

// 受控选择框
function ControlledSelect() {
  const [selected, setSelected] = useState('');
  
  return (
    <select
      value={selected}
      onChange={(e) => setSelected(e.target.value)}
    >
      <option value="">请选择</option>
      <option value="option1">选项1</option>
      <option value="option2">选项2</option>
      <option value="option3">选项3</option>
    </select>
  );
}

// 受控复选框
function ControlledCheckbox() {
  const [checked, setChecked] = useState(false);
  
  return (
    <label>
      <input
        type="checkbox"
        checked={checked}
        onChange={(e) => setChecked(e.target.checked)}
      />
      同意条款
    </label>
  );
}

// 受控单选按钮组
function ControlledRadio() {
  const [gender, setGender] = useState('');
  
  return (
    <div>
      <label>
        <input
          type="radio"
          value="male"
          checked={gender === 'male'}
          onChange={(e) => setGender(e.target.value)}
        />

      </label>
      <label>
        <input
          type="radio"
          value="female"
          checked={gender === 'female'}
          onChange={(e) => setGender(e.target.value)}
        />

      </label>
    </div>
  );
}

受控组件的优势

jsx
// 1. 实时验证
function ValidatedInput() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  
  const validateEmail = (value) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      setError('请输入有效的邮箱地址');
    } else {
      setError('');
    }
  };
  
  const handleChange = (e) => {
    const value = e.target.value;
    setEmail(value);
    validateEmail(value);
  };
  
  return (
    <div>
      <input
        type="email"
        value={email}
        onChange={handleChange}
        className={error ? 'error' : ''}
      />
      {error && <span className="error-message">{error}</span>}
    </div>
  );
}

// 2. 格式化输入
function FormattedPhoneInput() {
  const [phone, setPhone] = useState('');
  
  const formatPhone = (value) => {
    // 移除所有非数字字符
    const numbers = value.replace(/\D/g, '');
    
    // 限制长度
    const limited = numbers.slice(0, 11);
    
    // 格式化为 XXX-XXXX-XXXX
    if (limited.length <= 3) {
      return limited;
    } else if (limited.length <= 7) {
      return `${limited.slice(0, 3)}-${limited.slice(3)}`;
    } else {
      return `${limited.slice(0, 3)}-${limited.slice(3, 7)}-${limited.slice(7)}`;
    }
  };
  
  const handleChange = (e) => {
    const formatted = formatPhone(e.target.value);
    setPhone(formatted);
  };
  
  return (
    <input
      type="tel"
      value={phone}
      onChange={handleChange}
      placeholder="XXX-XXXX-XXXX"
    />
  );
}

// 3. 条件禁用
function ConditionalForm() {
  const [formData, setFormData] = useState({
    agreeTerms: false,
    age: '',
  });
  
  const canSubmit = formData.agreeTerms && parseInt(formData.age) >= 18;
  
  return (
    <form>
      <input
        type="number"
        value={formData.age}
        onChange={(e) => setFormData({ ...formData, age: e.target.value })}
        placeholder="年龄"
      />
      
      <label>
        <input
          type="checkbox"
          checked={formData.agreeTerms}
          onChange={(e) => setFormData({ ...formData, agreeTerms: e.target.checked })}
        />
        我已满18岁并同意条款
      </label>
      
      <button type="submit" disabled={!canSubmit}>
        提交
      </button>
    </form>
  );
}

// 4. 动态表单
function DynamicFieldForm() {
  const [fields, setFields] = useState([{ id: 1, value: '' }]);
  
  const addField = () => {
    setFields([...fields, { id: Date.now(), value: '' }]);
  };
  
  const removeField = (id) => {
    setFields(fields.filter(field => field.id !== id));
  };
  
  const updateField = (id, value) => {
    setFields(fields.map(field =>
      field.id === id ? { ...field, value } : field
    ));
  };
  
  return (
    <div>
      {fields.map(field => (
        <div key={field.id}>
          <input
            type="text"
            value={field.value}
            onChange={(e) => updateField(field.id, e.target.value)}
          />
          <button onClick={() => removeField(field.id)}>删除</button>
        </div>
      ))}
      <button onClick={addField}>添加字段</button>
    </div>
  );
}

受控组件的挑战

jsx
// 1. 性能问题 - 频繁渲染
function PerformanceIssue() {
  const [value, setValue] = useState('');
  
  console.log('组件重新渲染'); // 每次输入都会打印
  
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

// 解决方案: 使用防抖
import { useState, useCallback } from 'react';
import { debounce } from 'lodash';

function OptimizedInput() {
  const [displayValue, setDisplayValue] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  
  // 防抖搜索
  const debouncedSearch = useCallback(
    debounce((value) => {
      // 执行搜索
      performSearch(value).then(setSearchResults);
    }, 300),
    []
  );
  
  const handleChange = (e) => {
    const value = e.target.value;
    setDisplayValue(value);
    debouncedSearch(value);
  };
  
  return (
    <div>
      <input
        type="text"
        value={displayValue}
        onChange={handleChange}
      />
      <SearchResults results={searchResults} />
    </div>
  );
}

// 2. 复杂状态管理
function ComplexForm() {
  const [formData, setFormData] = useState({
    personal: {
      firstName: '',
      lastName: '',
      email: '',
    },
    address: {
      street: '',
      city: '',
      zip: '',
    },
    preferences: {
      newsletter: false,
      notifications: true,
    },
  });
  
  // 嵌套更新比较复杂
  const updatePersonal = (field, value) => {
    setFormData({
      ...formData,
      personal: {
        ...formData.personal,
        [field]: value,
      },
    });
  };
  
  // 使用immer简化嵌套更新
  import { useImmer } from 'use-immer';
  
  const [data, updateData] = useImmer({
    personal: { firstName: '', lastName: '' },
    address: { street: '', city: '' },
  });
  
  const updateField = (section, field, value) => {
    updateData(draft => {
      draft[section][field] = value;
    });
  };
  
  return (
    <input
      value={data.personal.firstName}
      onChange={(e) => updateField('personal', 'firstName', e.target.value)}
    />
  );
}

非受控组件

基础概念

非受控组件是指表单元素的值由DOM本身管理,React通过ref来访问DOM元素并获取值。

jsx
import { useRef } from 'react';

// 基础非受控输入框
function UncontrolledInput() {
  const inputRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('输入值:', inputRef.current.value);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}
        defaultValue="初始值"
      />
      <button type="submit">提交</button>
    </form>
  );
}

// 非受控文本域
function UncontrolledTextarea() {
  const textareaRef = useRef(null);
  
  const getValue = () => {
    return textareaRef.current.value;
  };
  
  return (
    <textarea
      ref={textareaRef}
      defaultValue="默认文本"
      rows={5}
    />
  );
}

// 非受控选择框
function UncontrolledSelect() {
  const selectRef = useRef(null);
  
  const getSelected = () => {
    return selectRef.current.value;
  };
  
  return (
    <select ref={selectRef} defaultValue="option2">
      <option value="option1">选项1</option>
      <option value="option2">选项2</option>
      <option value="option3">选项3</option>
    </select>
  );
}

// 非受控复选框
function UncontrolledCheckbox() {
  const checkboxRef = useRef(null);
  
  const isChecked = () => {
    return checkboxRef.current.checked;
  };
  
  return (
    <label>
      <input
        type="checkbox"
        ref={checkboxRef}
        defaultChecked={true}
      />
      记住我
    </label>
  );
}

// 文件输入(总是非受控)
function FileInput() {
  const fileInputRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    const files = fileInputRef.current.files;
    console.log('选择的文件:', files);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="file"
        ref={fileInputRef}
        multiple
      />
      <button type="submit">上传</button>
    </form>
  );
}

非受控组件的优势

jsx
// 1. 性能优化 - 减少渲染
function PerformantForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const messageRef = useRef(null);
  
  // 表单提交时才读取值,避免频繁渲染
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      message: messageRef.current.value,
    };
    
    console.log('表单数据:', formData);
    // 提交数据...
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={nameRef} placeholder="姓名" />
      <input type="email" ref={emailRef} placeholder="邮箱" />
      <textarea ref={messageRef} placeholder="消息" />
      <button type="submit">提交</button>
    </form>
  );
}

// 2. 与第三方库集成
function IntegrateWithLibrary() {
  const editorRef = useRef(null);
  
  useEffect(() => {
    // 初始化第三方编辑器
    const editor = new SomeEditor(editorRef.current, {
      // 配置...
    });
    
    return () => {
      editor.destroy();
    };
  }, []);
  
  const getValue = () => {
    // 通过ref获取第三方库的值
    return editorRef.current.getContent();
  };
  
  return <div ref={editorRef} />;
}

// 3. 表单重置更简单
function ResetForm() {
  const formRef = useRef(null);
  
  const handleReset = () => {
    formRef.current.reset();
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(formRef.current);
    console.log(Object.fromEntries(formData));
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="username" defaultValue="" />
      <input type="email" name="email" defaultValue="" />
      <button type="submit">提交</button>
      <button type="button" onClick={handleReset}>重置</button>
    </form>
  );
}

非受控组件的局限

jsx
// 1. 难以实现实时验证
function UncontrolledValidation() {
  const emailRef = useRef(null);
  const [error, setError] = useState('');
  
  // 只能在blur或submit时验证
  const handleBlur = () => {
    const value = emailRef.current.value;
    if (!value.includes('@')) {
      setError('请输入有效的邮箱');
    } else {
      setError('');
    }
  };
  
  return (
    <div>
      <input
        type="email"
        ref={emailRef}
        onBlur={handleBlur}
      />
      {error && <span>{error}</span>}
    </div>
  );
}

// 2. 难以实现条件渲染
function ConditionalRenderingIssue() {
  const ageRef = useRef(null);
  const [showAdultContent, setShowAdultContent] = useState(false);
  
  // 需要额外的事件处理来检查值
  const handleChange = () => {
    const age = parseInt(ageRef.current.value);
    setShowAdultContent(age >= 18);
  };
  
  return (
    <div>
      <input
        type="number"
        ref={ageRef}
        onChange={handleChange}
      />
      {showAdultContent && <div>成人内容</div>}
    </div>
  );
}

// 3. 难以同步多个输入
function SyncInputsIssue() {
  const input1Ref = useRef(null);
  const input2Ref = useRef(null);
  
  // 无法轻松保持两个输入同步
  const handleInput1Change = () => {
    // 需要手动设置另一个输入的值
    input2Ref.current.value = input1Ref.current.value;
  };
  
  return (
    <div>
      <input ref={input1Ref} onChange={handleInput1Change} />
      <input ref={input2Ref} />
    </div>
  );
}

混合使用策略

最佳实践场景

jsx
// 场景1: 简单表单 - 使用非受控
function SimpleContactForm() {
  const formRef = useRef(null);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(formRef.current);
    
    await fetch('/api/contact', {
      method: 'POST',
      body: formData,
    });
    
    formRef.current.reset();
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="name" required />
      <input type="email" name="email" required />
      <textarea name="message" required />
      <button type="submit">发送</button>
    </form>
  );
}

// 场景2: 需要验证的表单 - 使用受控
function ValidatedRegistrationForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    confirmPassword: '',
  });
  
  const [errors, setErrors] = useState({});
  
  const validate = () => {
    const newErrors = {};
    
    if (formData.username.length < 3) {
      newErrors.username = '用户名至少3个字符';
    }
    
    if (formData.password.length < 6) {
      newErrors.password = '密码至少6个字符';
    }
    
    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = '密码不匹配';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      console.log('提交表单:', formData);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.username}
        onChange={(e) => setFormData({ ...formData, username: e.target.value })}
      />
      {errors.username && <span>{errors.username}</span>}
      
      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
      />
      {errors.password && <span>{errors.password}</span>}
      
      <input
        type="password"
        value={formData.confirmPassword}
        onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
      />
      {errors.confirmPassword && <span>{errors.confirmPassword}</span>}
      
      <button type="submit">注册</button>
    </form>
  );
}

// 场景3: 混合使用
function HybridForm() {
  // 需要实时反馈的字段用受控
  const [email, setEmail] = useState('');
  const [emailValid, setEmailValid] = useState(false);
  
  // 不需要实时反馈的字段用非受控
  const nameRef = useRef(null);
  const phoneRef = useRef(null);
  
  useEffect(() => {
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    setEmailValid(isValid);
  }, [email]);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    const formData = {
      name: nameRef.current.value,
      phone: phoneRef.current.value,
      email: email,
    };
    
    console.log('提交:', formData);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={nameRef} placeholder="姓名" />
      <input type="tel" ref={phoneRef} placeholder="电话" />
      
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="邮箱"
      />
      {email && (
        <span>{emailValid ? '✓ 有效' : '✗ 无效'}</span>
      )}
      
      <button type="submit">提交</button>
    </form>
  );
}

转换策略

jsx
// 受控转非受控
function ControlledToUncontrolled() {
  // 受控版本
  const [value, setValue] = useState('initial');
  
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
  
  // 转换为非受控版本
  const inputRef = useRef(null);
  
  return (
    <input
      ref={inputRef}
      defaultValue="initial"
    />
  );
}

// 非受控转受控
function UncontrolledToControlled() {
  // 非受控版本
  const inputRef = useRef(null);
  
  return (
    <input
      ref={inputRef}
      defaultValue="initial"
    />
  );
  
  // 转换为受控版本
  const [value, setValue] = useState('initial');
  
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

// 动态切换(避免)
function AvoidDynamicSwitch() {
  const [controlled, setControlled] = useState(true);
  const [value, setValue] = useState('');
  
  // ❌ 不要这样做 - 会导致警告
  return (
    <input
      value={controlled ? value : undefined}
      onChange={(e) => setValue(e.target.value)}
    />
  );
  
  // ✅ 正确做法 - 保持一致
  return (
    <input
      value={value || ''}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

高级模式

受控组件高级技巧

jsx
// 1. 受控组件工厂
function createControlledInput(Component) {
  return function ControlledWrapper({ value, onChange, ...props }) {
    const [internalValue, setInternalValue] = useState(value || '');
    
    useEffect(() => {
      if (value !== undefined) {
        setInternalValue(value);
      }
    }, [value]);
    
    const handleChange = (e) => {
      const newValue = e.target.value;
      setInternalValue(newValue);
      onChange?.(newValue);
    };
    
    return (
      <Component
        {...props}
        value={internalValue}
        onChange={handleChange}
      />
    );
  };
}

const ControlledInput = createControlledInput('input');
const ControlledTextarea = createControlledInput('textarea');

// 2. 延迟受控
function DelayedControlledInput({ value, onChange, delay = 300 }) {
  const [displayValue, setDisplayValue] = useState(value);
  const timeoutRef = useRef(null);
  
  useEffect(() => {
    setDisplayValue(value);
  }, [value]);
  
  const handleChange = (e) => {
    const newValue = e.target.value;
    setDisplayValue(newValue);
    
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    timeoutRef.current = setTimeout(() => {
      onChange(newValue);
    }, delay);
  };
  
  return (
    <input
      type="text"
      value={displayValue}
      onChange={handleChange}
    />
  );
}

// 3. 受控组件with缓存
function CachedControlledInput({ value, onChange }) {
  const [cache, setCache] = useState(value);
  const [isDirty, setIsDirty] = useState(false);
  
  const handleChange = (e) => {
    const newValue = e.target.value;
    setCache(newValue);
    setIsDirty(newValue !== value);
  };
  
  const handleSave = () => {
    onChange(cache);
    setIsDirty(false);
  };
  
  const handleCancel = () => {
    setCache(value);
    setIsDirty(false);
  };
  
  return (
    <div>
      <input
        type="text"
        value={cache}
        onChange={handleChange}
      />
      {isDirty && (
        <>
          <button onClick={handleSave}>保存</button>
          <button onClick={handleCancel}>取消</button>
        </>
      )}
    </div>
  );
}

非受控组件高级技巧

jsx
// 1. 带默认值的非受控组件
function UncontrolledWithDefault({ defaultValue, onSubmit }) {
  const inputRef = useRef(null);
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    if (!mounted && defaultValue) {
      inputRef.current.value = defaultValue;
      setMounted(true);
    }
  }, [defaultValue, mounted]);
  
  const handleSubmit = () => {
    onSubmit(inputRef.current.value);
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={handleSubmit}>提交</button>
    </div>
  );
}

// 2. 非受控组件监听器
function UncontrolledWithListener({ onChange }) {
  const inputRef = useRef(null);
  
  useEffect(() => {
    const input = inputRef.current;
    
    const handleInput = (e) => {
      onChange?.(e.target.value);
    };
    
    input.addEventListener('input', handleInput);
    
    return () => {
      input.removeEventListener('input', handleInput);
    };
  }, [onChange]);
  
  return <input ref={inputRef} />;
}

// 3. 非受控表单验证
function UncontrolledFormValidation() {
  const formRef = useRef(null);
  const [errors, setErrors] = useState({});
  
  const validate = () => {
    const form = formRef.current;
    const formData = new FormData(form);
    const newErrors = {};
    
    const email = formData.get('email');
    if (!email.includes('@')) {
      newErrors.email = '无效的邮箱';
    }
    
    const age = formData.get('age');
    if (age < 18) {
      newErrors.age = '必须年满18岁';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (validate()) {
      const formData = new FormData(formRef.current);
      console.log(Object.fromEntries(formData));
    }
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="email" name="email" />
      {errors.email && <span>{errors.email}</span>}
      
      <input type="number" name="age" />
      {errors.age && <span>{errors.age}</span>}
      
      <button type="submit">提交</button>
    </form>
  );
}

性能优化

受控组件优化

jsx
// 1. 使用useCallback避免重新创建函数
function OptimizedControlled() {
  const [value, setValue] = useState('');
  
  const handleChange = useCallback((e) => {
    setValue(e.target.value);
  }, []);
  
  return (
    <input
      type="text"
      value={value}
      onChange={handleChange}
    />
  );
}

// 2. 分离子组件减少渲染范围
const Input = React.memo(function Input({ value, onChange }) {
  console.log('Input渲染');
  return (
    <input
      type="text"
      value={value}
      onChange={onChange}
    />
  );
});

function FormWithMemo() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  const handleNameChange = useCallback((e) => {
    setName(e.target.value);
  }, []);
  
  const handleEmailChange = useCallback((e) => {
    setEmail(e.target.value);
  }, []);
  
  return (
    <div>
      <Input value={name} onChange={handleNameChange} />
      <Input value={email} onChange={handleEmailChange} />
    </div>
  );
}

// 3. 使用防抖节流
import { useDebouncedCallback } from 'use-debounce';

function DebouncedSearch() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const debouncedSearch = useDebouncedCallback(
    (value) => {
      performSearch(value).then(setResults);
    },
    300
  );
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };
  
  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleChange}
      />
      <Results data={results} />
    </div>
  );
}

// 4. 虚拟化长列表
import { FixedSizeList } from 'react-window';

function VirtualizedControlledList() {
  const [items, setItems] = useState(
    Array.from({ length: 10000 }, (_, i) => ({
      id: i,
      checked: false,
    }))
  );
  
  const handleToggle = (id) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, checked: !item.checked } : item
    ));
  };
  
  const Row = ({ index, style }) => (
    <div style={style}>
      <input
        type="checkbox"
        checked={items[index].checked}
        onChange={() => handleToggle(items[index].id)}
      />
      Item {items[index].id}
    </div>
  );
  
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

非受控组件优化

jsx
// 1. 批量读取表单值
function BatchRead() {
  const formRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    // 一次性读取所有值
    const formData = new FormData(formRef.current);
    const values = Object.fromEntries(formData);
    
    console.log(values);
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="field1" />
      <input type="text" name="field2" />
      <input type="text" name="field3" />
      <button type="submit">提交</button>
    </form>
  );
}

// 2. 使用原生表单验证
function NativeValidation() {
  const formRef = useRef(null);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (formRef.current.checkValidity()) {
      const formData = new FormData(formRef.current);
      console.log(Object.fromEntries(formData));
    }
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input
        type="email"
        name="email"
        required
        pattern="[^@]+@[^@]+\.[^@]+"
      />
      <input
        type="number"
        name="age"
        required
        min="18"
        max="100"
      />
      <button type="submit">提交</button>
    </form>
  );
}

实战案例

搜索表单

jsx
// 受控版本 - 实时搜索
function ControlledSearchForm() {
  const [searchTerm, setSearchTerm] = useState('');
  const [filters, setFilters] = useState({
    category: '',
    priceRange: '',
    inStock: false,
  });
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    const delayDebounce = setTimeout(() => {
      if (searchTerm) {
        performSearch(searchTerm, filters).then(setResults);
      }
    }, 300);
    
    return () => clearTimeout(delayDebounce);
  }, [searchTerm, filters]);
  
  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="搜索..."
      />
      
      <select
        value={filters.category}
        onChange={(e) => setFilters({ ...filters, category: e.target.value })}
      >
        <option value="">所有分类</option>
        <option value="electronics">电子产品</option>
        <option value="clothing">服装</option>
      </select>
      
      <label>
        <input
          type="checkbox"
          checked={filters.inStock}
          onChange={(e) => setFilters({ ...filters, inStock: e.target.checked })}
        />
        仅显示有货
      </label>
      
      <SearchResults results={results} />
    </div>
  );
}

// 非受控版本 - 提交时搜索
function UncontrolledSearchForm() {
  const formRef = useRef(null);
  const [results, setResults] = useState([]);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const formData = new FormData(formRef.current);
    const searchParams = {
      term: formData.get('searchTerm'),
      category: formData.get('category'),
      inStock: formData.get('inStock') === 'on',
    };
    
    const data = await performSearch(searchParams);
    setResults(data);
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input
        type="text"
        name="searchTerm"
        placeholder="搜索..."
      />
      
      <select name="category">
        <option value="">所有分类</option>
        <option value="electronics">电子产品</option>
        <option value="clothing">服装</option>
      </select>
      
      <label>
        <input type="checkbox" name="inStock" />
        仅显示有货
      </label>
      
      <button type="submit">搜索</button>
      
      <SearchResults results={results} />
    </form>
  );
}

多步骤表单

jsx
// 混合策略
function MultiStepForm() {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({});
  
  // 第一步 - 受控(需要验证)
  const Step1 = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [errors, setErrors] = useState({});
    
    const validateStep1 = () => {
      const newErrors = {};
      if (!email.includes('@')) newErrors.email = '无效的邮箱';
      if (password.length < 6) newErrors.password = '密码至少6位';
      setErrors(newErrors);
      return Object.keys(newErrors).length === 0;
    };
    
    const handleNext = () => {
      if (validateStep1()) {
        setFormData({ ...formData, email, password });
        setStep(2);
      }
    };
    
    return (
      <div>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        {errors.email && <span>{errors.email}</span>}
        
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        {errors.password && <span>{errors.password}</span>}
        
        <button onClick={handleNext}>下一步</button>
      </div>
    );
  };
  
  // 第二步 - 非受控(不需要实时验证)
  const Step2 = () => {
    const formRef = useRef(null);
    
    const handleNext = () => {
      const data = new FormData(formRef.current);
      setFormData({
        ...formData,
        firstName: data.get('firstName'),
        lastName: data.get('lastName'),
        phone: data.get('phone'),
      });
      setStep(3);
    };
    
    return (
      <form ref={formRef}>
        <input type="text" name="firstName" required />
        <input type="text" name="lastName" required />
        <input type="tel" name="phone" required />
        
        <button type="button" onClick={() => setStep(1)}>上一步</button>
        <button type="button" onClick={handleNext}>下一步</button>
      </form>
    );
  };
  
  // 第三步 - 确认
  const Step3 = () => {
    const handleSubmit = async () => {
      await submitForm(formData);
    };
    
    return (
      <div>
        <h3>确认信息</h3>
        <pre>{JSON.stringify(formData, null, 2)}</pre>
        
        <button onClick={() => setStep(2)}>上一步</button>
        <button onClick={handleSubmit}>提交</button>
      </div>
    );
  };
  
  return (
    <div>
      {step === 1 && <Step1 />}
      {step === 2 && <Step2 />}
      {step === 3 && <Step3 />}
    </div>
  );
}

总结

受控与非受控组件选择要点:

  1. 受控组件:需要实时验证、格式化、条件渲染时使用
  2. 非受控组件:简单表单、性能敏感、第三方库集成时使用
  3. 混合使用:根据具体字段需求灵活选择
  4. 性能优化:防抖节流、memo、虚拟化
  5. 避免陷阱:不要动态切换受控/非受控状态

选择合适的方式,能够在功能性和性能之间取得最佳平衡。