Skip to content

表单状态管理

概述

在复杂的React应用中,表单状态管理是一个关键挑战。从简单的登录表单到复杂的多步骤表单,合理的状态管理策略能够显著提升代码质量和用户体验。本文将深入探讨React中表单状态管理的各种方案和最佳实践。

基础状态管理

useState管理表单

jsx
import { useState } from 'react';

// 单个字段
function SimpleForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ email, password });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">登录</button>
    </form>
  );
}

// 对象管理多个字段
function FormWithObject() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    rememberMe: false,
  });
  
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData({
      ...formData,
      [name]: type === 'checkbox' ? checked : value,
    });
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(formData);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="username"
        value={formData.username}
        onChange={handleChange}
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleChange}
      />
      <label>
        <input
          type="checkbox"
          name="rememberMe"
          checked={formData.rememberMe}
          onChange={handleChange}
        />
        记住我
      </label>
      <button type="submit">注册</button>
    </form>
  );
}

// 嵌套对象管理
function NestedFormData() {
  const [formData, setFormData] = useState({
    personal: {
      firstName: '',
      lastName: '',
      email: '',
    },
    address: {
      street: '',
      city: '',
      zipCode: '',
    },
    preferences: {
      newsletter: false,
      notifications: true,
    },
  });
  
  const handleChange = (section, field, value) => {
    setFormData({
      ...formData,
      [section]: {
        ...formData[section],
        [field]: value,
      },
    });
  };
  
  return (
    <form>
      <input
        type="text"
        value={formData.personal.firstName}
        onChange={(e) => handleChange('personal', 'firstName', e.target.value)}
      />
      <input
        type="text"
        value={formData.address.city}
        onChange={(e) => handleChange('address', 'city', e.target.value)}
      />
      <input
        type="checkbox"
        checked={formData.preferences.newsletter}
        onChange={(e) => handleChange('preferences', 'newsletter', e.target.checked)}
      />
    </form>
  );
}

useReducer管理复杂表单

jsx
import { useReducer } from 'react';

// 表单reducer
function formReducer(state, action) {
  switch (action.type) {
    case 'UPDATE_FIELD':
      return {
        ...state,
        [action.field]: action.value,
      };
    
    case 'UPDATE_NESTED_FIELD':
      return {
        ...state,
        [action.section]: {
          ...state[action.section],
          [action.field]: action.value,
        },
      };
    
    case 'SET_ERRORS':
      return {
        ...state,
        errors: action.errors,
      };
    
    case 'RESET_FORM':
      return action.initialState;
    
    case 'SUBMIT_START':
      return {
        ...state,
        submitting: true,
        submitError: null,
      };
    
    case 'SUBMIT_SUCCESS':
      return {
        ...state,
        submitting: false,
        submitSuccess: true,
      };
    
    case 'SUBMIT_ERROR':
      return {
        ...state,
        submitting: false,
        submitError: action.error,
      };
    
    default:
      return state;
  }
}

function FormWithReducer() {
  const initialState = {
    username: '',
    email: '',
    password: '',
    errors: {},
    submitting: false,
    submitSuccess: false,
    submitError: null,
  };
  
  const [state, dispatch] = useReducer(formReducer, initialState);
  
  const handleChange = (field, value) => {
    dispatch({ type: 'UPDATE_FIELD', field, value });
  };
  
  const validate = () => {
    const errors = {};
    
    if (!state.username) {
      errors.username = '用户名不能为空';
    }
    
    if (!state.email.includes('@')) {
      errors.email = '邮箱格式不正确';
    }
    
    if (state.password.length < 6) {
      errors.password = '密码至少6个字符';
    }
    
    dispatch({ type: 'SET_ERRORS', errors });
    return Object.keys(errors).length === 0;
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    if (!validate()) return;
    
    dispatch({ type: 'SUBMIT_START' });
    
    try {
      await submitForm(state);
      dispatch({ type: 'SUBMIT_SUCCESS' });
    } catch (error) {
      dispatch({ type: 'SUBMIT_ERROR', error: error.message });
    }
  };
  
  const handleReset = () => {
    dispatch({ type: 'RESET_FORM', initialState });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={state.username}
        onChange={(e) => handleChange('username', e.target.value)}
      />
      {state.errors.username && <span>{state.errors.username}</span>}
      
      <input
        type="email"
        value={state.email}
        onChange={(e) => handleChange('email', e.target.value)}
      />
      {state.errors.email && <span>{state.errors.email}</span>}
      
      <input
        type="password"
        value={state.password}
        onChange={(e) => handleChange('password', e.target.value)}
      />
      {state.errors.password && <span>{state.errors.password}</span>}
      
      <button type="submit" disabled={state.submitting}>
        {state.submitting ? '提交中...' : '提交'}
      </button>
      <button type="button" onClick={handleReset}>重置</button>
      
      {state.submitSuccess && <div>提交成功!</div>}
      {state.submitError && <div>错误: {state.submitError}</div>}
    </form>
  );
}

自定义Hook封装

useForm Hook

jsx
import { useState, useCallback } from 'react';

function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  const handleChange = useCallback((e) => {
    const { name, value, type, checked } = e.target;
    setValues(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value,
    }));
  }, []);
  
  const handleBlur = useCallback((e) => {
    const { name } = e.target;
    setTouched(prev => ({
      ...prev,
      [name]: true,
    }));
  }, []);
  
  const handleSubmit = useCallback(async (onSubmit) => {
    return async (e) => {
      e.preventDefault();
      
      const validationErrors = validate(values);
      setErrors(validationErrors);
      
      if (Object.keys(validationErrors).length === 0) {
        setIsSubmitting(true);
        try {
          await onSubmit(values);
        } catch (error) {
          console.error('提交错误:', error);
        } finally {
          setIsSubmitting(false);
        }
      }
    };
  }, [values, validate]);
  
  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
    setIsSubmitting(false);
  }, [initialValues]);
  
  const setFieldValue = useCallback((field, value) => {
    setValues(prev => ({
      ...prev,
      [field]: value,
    }));
  }, []);
  
  const setFieldError = useCallback((field, error) => {
    setErrors(prev => ({
      ...prev,
      [field]: error,
    }));
  }, []);
  
  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset,
    setFieldValue,
    setFieldError,
  };
}

// 使用示例
function LoginForm() {
  const initialValues = {
    email: '',
    password: '',
  };
  
  const validate = (values) => {
    const errors = {};
    
    if (!values.email) {
      errors.email = '邮箱不能为空';
    } else if (!/\S+@\S+\.\S+/.test(values.email)) {
      errors.email = '邮箱格式不正确';
    }
    
    if (!values.password) {
      errors.password = '密码不能为空';
    } else if (values.password.length < 6) {
      errors.password = '密码至少6个字符';
    }
    
    return errors;
  };
  
  const {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit,
    reset,
  } = useForm(initialValues, validate);
  
  const onSubmit = async (values) => {
    console.log('提交:', values);
    // API调用
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          type="email"
          name="email"
          value={values.email}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.email && errors.email && (
          <span>{errors.email}</span>
        )}
      </div>
      
      <div>
        <input
          type="password"
          name="password"
          value={values.password}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.password && errors.password && (
          <span>{errors.password}</span>
        )}
      </div>
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '登录中...' : '登录'}
      </button>
      <button type="button" onClick={reset}>重置</button>
    </form>
  );
}

useFieldArray Hook

jsx
function useFieldArray(name, initialValue = []) {
  const [fields, setFields] = useState(initialValue);
  
  const append = useCallback((value) => {
    setFields(prev => [...prev, value]);
  }, []);
  
  const prepend = useCallback((value) => {
    setFields(prev => [value, ...prev]);
  }, []);
  
  const remove = useCallback((index) => {
    setFields(prev => prev.filter((_, i) => i !== index));
  }, []);
  
  const insert = useCallback((index, value) => {
    setFields(prev => [
      ...prev.slice(0, index),
      value,
      ...prev.slice(index),
    ]);
  }, []);
  
  const update = useCallback((index, value) => {
    setFields(prev => prev.map((item, i) => 
      i === index ? value : item
    ));
  }, []);
  
  const move = useCallback((from, to) => {
    setFields(prev => {
      const newFields = [...prev];
      const [removed] = newFields.splice(from, 1);
      newFields.splice(to, 0, removed);
      return newFields;
    });
  }, []);
  
  const swap = useCallback((indexA, indexB) => {
    setFields(prev => {
      const newFields = [...prev];
      [newFields[indexA], newFields[indexB]] = [newFields[indexB], newFields[indexA]];
      return newFields;
    });
  }, []);
  
  const reset = useCallback(() => {
    setFields(initialValue);
  }, [initialValue]);
  
  return {
    fields,
    append,
    prepend,
    remove,
    insert,
    update,
    move,
    swap,
    reset,
  };
}

// 使用示例
function TodoListForm() {
  const {
    fields: todos,
    append,
    remove,
    update,
  } = useFieldArray('todos', []);
  
  const [newTodo, setNewTodo] = useState('');
  
  const handleAdd = () => {
    if (newTodo.trim()) {
      append({ id: Date.now(), text: newTodo, completed: false });
      setNewTodo('');
    }
  };
  
  const handleToggle = (index) => {
    update(index, {
      ...todos[index],
      completed: !todos[index].completed,
    });
  };
  
  return (
    <div>
      <div>
        <input
          type="text"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="添加待办事项"
        />
        <button onClick={handleAdd}>添加</button>
      </div>
      
      <ul>
        {todos.map((todo, index) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => handleToggle(index)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => remove(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

表单验证状态

实时验证

jsx
function RealTimeValidation() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
  });
  
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validateField = (name, value) => {
    let error = '';
    
    switch (name) {
      case 'username':
        if (!value) {
          error = '用户名不能为空';
        } else if (value.length < 3) {
          error = '用户名至少3个字符';
        } else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
          error = '用户名只能包含字母、数字和下划线';
        }
        break;
      
      case 'email':
        if (!value) {
          error = '邮箱不能为空';
        } else if (!/\S+@\S+\.\S+/.test(value)) {
          error = '邮箱格式不正确';
        }
        break;
      
      case 'password':
        if (!value) {
          error = '密码不能为空';
        } else if (value.length < 8) {
          error = '密码至少8个字符';
        } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
          error = '密码必须包含大小写字母和数字';
        }
        break;
    }
    
    return error;
  };
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    
    setFormData({
      ...formData,
      [name]: value,
    });
    
    if (touched[name]) {
      const error = validateField(name, value);
      setErrors({
        ...errors,
        [name]: error,
      });
    }
  };
  
  const handleBlur = (e) => {
    const { name, value } = e.target;
    
    setTouched({
      ...touched,
      [name]: true,
    });
    
    const error = validateField(name, value);
    setErrors({
      ...errors,
      [name]: error,
    });
  };
  
  const isValid = () => {
    return Object.values(errors).every(error => !error) &&
           Object.keys(touched).length === Object.keys(formData).length;
  };
  
  return (
    <form>
      <div>
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.username && errors.username && (
          <span className="error">{errors.username}</span>
        )}
      </div>
      
      <div>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.email && errors.email && (
          <span className="error">{errors.email}</span>
        )}
      </div>
      
      <div>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
          onBlur={handleBlur}
        />
        {touched.password && errors.password && (
          <span className="error">{errors.password}</span>
        )}
      </div>
      
      <button type="submit" disabled={!isValid()}>
        提交
      </button>
    </form>
  );
}

异步验证

jsx
function AsyncValidation() {
  const [username, setUsername] = useState('');
  const [checking, setChecking] = useState(false);
  const [available, setAvailable] = useState(null);
  
  useEffect(() => {
    const checkUsername = async () => {
      if (username.length < 3) {
        setAvailable(null);
        return;
      }
      
      setChecking(true);
      
      try {
        const response = await fetch(`/api/check-username?username=${username}`);
        const data = await response.json();
        setAvailable(data.available);
      } catch (error) {
        console.error('检查用户名失败:', error);
      } finally {
        setChecking(false);
      }
    };
    
    const debounceTimer = setTimeout(checkUsername, 500);
    
    return () => clearTimeout(debounceTimer);
  }, [username]);
  
  return (
    <div>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="用户名"
      />
      
      {checking && <span>检查中...</span>}
      
      {!checking && available === true && (
        <span className="success">✓ 用户名可用</span>
      )}
      
      {!checking && available === false && (
        <span className="error">✗ 用户名已被使用</span>
      )}
    </div>
  );
}

// 复杂的异步验证Hook
function useAsyncValidation(validateFn, dependencies = []) {
  const [validating, setValidating] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const validate = async () => {
      setValidating(true);
      setError(null);
      
      try {
        const error = await validateFn(...dependencies);
        
        if (!cancelled) {
          setError(error);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setValidating(false);
        }
      }
    };
    
    const debounceTimer = setTimeout(validate, 500);
    
    return () => {
      cancelled = true;
      clearTimeout(debounceTimer);
    };
  }, dependencies);
  
  return { validating, error };
}

// 使用异步验证Hook
function RegistrationForm() {
  const [email, setEmail] = useState('');
  
  const validateEmail = async (email) => {
    if (!email) return null;
    
    const response = await fetch(`/api/validate-email?email=${email}`);
    const data = await response.json();
    
    return data.error || null;
  };
  
  const { validating, error } = useAsyncValidation(validateEmail, [email]);
  
  return (
    <div>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      
      {validating && <span>验证中...</span>}
      {error && <span className="error">{error}</span>}
    </div>
  );
}

表单提交状态

提交流程管理

jsx
function SubmitStateManagement() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
  });
  
  const [submitState, setSubmitState] = useState({
    status: 'idle', // 'idle' | 'submitting' | 'success' | 'error'
    error: null,
  });
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    setSubmitState({ status: 'submitting', error: null });
    
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData),
      });
      
      if (!response.ok) {
        throw new Error('登录失败');
      }
      
      const data = await response.json();
      
      setSubmitState({ status: 'success', error: null });
      
      // 重定向或其他操作
      setTimeout(() => {
        window.location.href = '/dashboard';
      }, 1000);
      
    } catch (error) {
      setSubmitState({
        status: 'error',
        error: error.message,
      });
    }
  };
  
  const canSubmit = () => {
    return formData.email && formData.password && submitState.status !== 'submitting';
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        disabled={submitState.status === 'submitting'}
      />
      
      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        disabled={submitState.status === 'submitting'}
      />
      
      <button type="submit" disabled={!canSubmit()}>
        {submitState.status === 'submitting' ? '登录中...' : '登录'}
      </button>
      
      {submitState.status === 'error' && (
        <div className="error">{submitState.error}</div>
      )}
      
      {submitState.status === 'success' && (
        <div className="success">登录成功!正在跳转...</div>
      )}
    </form>
  );
}

乐观更新

jsx
function OptimisticUpdate() {
  const [todos, setTodos] = useState([]);
  const [optimisticTodos, setOptimisticTodos] = useState([]);
  
  useEffect(() => {
    setOptimisticTodos(todos);
  }, [todos]);
  
  const addTodo = async (text) => {
    const tempId = `temp-${Date.now()}`;
    const newTodo = { id: tempId, text, completed: false };
    
    // 乐观更新UI
    setOptimisticTodos(prev => [...prev, newTodo]);
    
    try {
      const response = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text }),
      });
      
      const savedTodo = await response.json();
      
      // 替换临时ID
      setTodos(prev => [...prev, savedTodo]);
      
    } catch (error) {
      // 回滚乐观更新
      setOptimisticTodos(prev => prev.filter(todo => todo.id !== tempId));
      console.error('添加失败:', error);
    }
  };
  
  const toggleTodo = async (id) => {
    // 乐观更新
    setOptimisticTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
    
    try {
      await fetch(`/api/todos/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          completed: !todos.find(t => t.id === id).completed,
        }),
      });
      
      // 更新实际数据
      setTodos(prev =>
        prev.map(todo =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        )
      );
      
    } catch (error) {
      // 回滚
      setOptimisticTodos(todos);
      console.error('更新失败:', error);
    }
  };
  
  return (
    <div>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{
              textDecoration: todo.completed ? 'line-through' : 'none',
              opacity: todo.id.toString().startsWith('temp-') ? 0.5 : 1,
            }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

表单重置和清空

重置策略

jsx
function FormResetStrategies() {
  const initialValues = {
    username: '',
    email: '',
    bio: '',
  };
  
  const [formData, setFormData] = useState(initialValues);
  const [pristine, setPristine] = useState(true);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
    setPristine(false);
  };
  
  // 方式1: 重置到初始值
  const resetToInitial = () => {
    setFormData(initialValues);
    setPristine(true);
  };
  
  // 方式2: 重置到上次提交的值
  const [lastSubmitted, setLastSubmitted] = useState(initialValues);
  
  const resetToLastSubmitted = () => {
    setFormData(lastSubmitted);
    setPristine(true);
  };
  
  // 方式3: 清空所有字段
  const clearAll = () => {
    setFormData(Object.keys(formData).reduce((acc, key) => {
      acc[key] = '';
      return acc;
    }, {}));
    setPristine(true);
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      await submitForm(formData);
      setLastSubmitted(formData);
      setPristine(true);
    } catch (error) {
      console.error('提交失败:', error);
    }
  };
  
  // 离开前确认
  useEffect(() => {
    const handleBeforeUnload = (e) => {
      if (!pristine) {
        e.preventDefault();
        e.returnValue = '';
      }
    };
    
    window.addEventListener('beforeunload', handleBeforeUnload);
    
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
    };
  }, [pristine]);
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="username"
        value={formData.username}
        onChange={handleChange}
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <textarea
        name="bio"
        value={formData.bio}
        onChange={handleChange}
      />
      
      <button type="submit">提交</button>
      <button type="button" onClick={resetToInitial}>重置</button>
      <button type="button" onClick={resetToLastSubmitted}>恢复</button>
      <button type="button" onClick={clearAll}>清空</button>
      
      {!pristine && <span>有未保存的更改</span>}
    </form>
  );
}

表单状态持久化

LocalStorage持久化

jsx
function PersistentForm() {
  const STORAGE_KEY = 'contact-form-draft';
  
  const [formData, setFormData] = useState(() => {
    const saved = localStorage.getItem(STORAGE_KEY);
    return saved ? JSON.parse(saved) : {
      name: '',
      email: '',
      message: '',
    };
  });
  
  // 自动保存
  useEffect(() => {
    const saveTimer = setTimeout(() => {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
    }, 1000);
    
    return () => clearTimeout(saveTimer);
  }, [formData]);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      await submitForm(formData);
      
      // 提交成功后清除草稿
      localStorage.removeItem(STORAGE_KEY);
      
      setFormData({ name: '', email: '', message: '' });
    } catch (error) {
      console.error('提交失败:', error);
    }
  };
  
  const clearDraft = () => {
    localStorage.removeItem(STORAGE_KEY);
    setFormData({ name: '', email: '', message: '' });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        placeholder="姓名"
      />
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        placeholder="邮箱"
      />
      <textarea
        value={formData.message}
        onChange={(e) => setFormData({ ...formData, message: e.target.value })}
        placeholder="消息"
      />
      
      <button type="submit">发送</button>
      <button type="button" onClick={clearDraft}>清除草稿</button>
      
      <small>表单内容已自动保存</small>
    </form>
  );
}

SessionStorage for多步骤表单

jsx
function MultiStepFormWithPersistence() {
  const SESSION_KEY = 'multi-step-form';
  
  const [step, setStep] = useState(() => {
    const saved = sessionStorage.getItem(SESSION_KEY);
    return saved ? JSON.parse(saved).step : 1;
  });
  
  const [formData, setFormData] = useState(() => {
    const saved = sessionStorage.getItem(SESSION_KEY);
    return saved ? JSON.parse(saved).data : {
      personal: {},
      address: {},
      payment: {},
    };
  });
  
  useEffect(() => {
    sessionStorage.setItem(SESSION_KEY, JSON.stringify({
      step,
      data: formData,
    }));
  }, [step, formData]);
  
  const nextStep = () => setStep(step + 1);
  const prevStep = () => setStep(step - 1);
  
  const updateSection = (section, data) => {
    setFormData({
      ...formData,
      [section]: { ...formData[section], ...data },
    });
  };
  
  const handleSubmit = async () => {
    try {
      await submitForm(formData);
      sessionStorage.removeItem(SESSION_KEY);
    } catch (error) {
      console.error('提交失败:', error);
    }
  };
  
  return (
    <div>
      {step === 1 && (
        <Step1
          data={formData.personal}
          onNext={(data) => {
            updateSection('personal', data);
            nextStep();
          }}
        />
      )}
      
      {step === 2 && (
        <Step2
          data={formData.address}
          onNext={(data) => {
            updateSection('address', data);
            nextStep();
          }}
          onPrev={prevStep}
        />
      )}
      
      {step === 3 && (
        <Step3
          data={formData.payment}
          onSubmit={(data) => {
            updateSection('payment', data);
            handleSubmit();
          }}
          onPrev={prevStep}
        />
      )}
    </div>
  );
}

总结

表单状态管理要点:

  1. 基础方案:useState适合简单表单,useReducer适合复杂表单
  2. 自定义Hook:封装通用逻辑,提高复用性
  3. 验证策略:实时验证、异步验证、提交时验证
  4. 提交管理:状态跟踪、错误处理、乐观更新
  5. 重置策略:多种重置方式,未保存提醒
  6. 持久化:LocalStorage、SessionStorage保存草稿

选择合适的状态管理方案能够显著提升表单开发效率和用户体验。