react 表单管理

受控模式

严格来说,虽然从业务上来说这是一个表单,但从编码上来看 并没有用到 form

其中共涉及到如下定义和维护的状态:

  • 表单本身数据的状态 formData(由于使用了受控组件,因此需要为该字段提供默认值)
  • 提交中状态 isPending
  • 提交结果状态 submitResult
import _ from 'lodash';
import cn from '@/utils/cn';
import * as api from '../api';
import { useState } from 'react';
import { match } from 'ts-pattern';

type SubmitResult = { type: 'idle' | 'success' | 'error'; data: string };
type FormDataState = { uname: string; comment: string };

const initDate = {
  submitResult: { type: 'idle', data: '' },
  formData: { uname: '', comment: '' },
};

// React 18实现(受控来管理"表单")
export default function formDemo() {
  const [isPending, setIsPending] = useState(false);
  const [submitResult, setSubmitResult] = useState<SubmitResult>(_.cloneDeep(initDate.submitResult));
  const [formData, setFormData] = useState<FormDataState>(_.cloneDeep(initDate.formData));

  // 提交操作
  const handleSubmit = async () => {
    setIsPending(true);
    const finalFormData = new FormData();
    Object.entries(formData).forEach(([key, value]) => {
      finalFormData.append(key, value);
    });
    const res = await api.submitComment(finalFormData);
    setSubmitResult({ type: res.code === 0 ? 'success' : 'error', data: res.msg });
    setIsPending(false);
  };

    // 重置操作
  const handleReset = () => {
    setFormData(_.cloneDeep(initDate.formData));
    setSubmitResult(_.cloneDeep(initDate.submitResult));
  };

  return (
    <div className="w-auto">
      <div className={cn('flex flex-col gap-2', { 'opacity-50': isPending })}>
        <div>
          <label>昵称:</label>
          <input name="uname" value={formData.uname} onChange={(e) => setFormData({ ...formData, uname: e.target.value })} className="border rounded px-2" />
        </div>
        <div>
          <label>评论:</label>
          <input name="comment" value={formData.comment} onChange={(e) => setFormData({ ...formData, comment: e.target.value })} className="border rounded px-2" />
        </div>

        <div className="flex gap-2">
          <button className="bg-blue-400 text-white px-2 rounded w-20 p-1" onClick={handleSubmit}>
            发表
          </button>
          <button className="bg-red-400 text-white px-2 rounded w-20 p-1" onClick={handleReset}>
            重置
          </button>
        </div>
      </div>

      <hr className="my-2" />

      {match(isPending)
        .with(true, () => <span className="text-yellow-400 text-xs">提交中...</span>)
        .otherwise(() =>
          match(submitResult.type)
            .with('error', () => <span className="text-red-400 text-xs">{submitResult.data}</span>)
            .with('success', () => <span className="text-green-400 text-xs">{submitResult.data}</span>)
            .otherwise(() => null),
        )}
    </div>
  );
}

非受控模式

在受控下我们可以看到,要完成一个表单的基本能力,需要定义一堆的状态来维护。这显然有点繁琐,尤其是遇到大量的表单字段的时候!
那我们为何不考虑非受控 原生表单呢?
这种模式下,你无需关心表单本身的字段状态,仅定义 isPending 和 submitResult 即可!

import _ from 'lodash';
import cn from '@/utils/cn';
import * as api from '../api';
import { useState } from 'react';
import { match } from 'ts-pattern';

type SubmitResult = { type: 'idle' | 'success' | 'error'; data: string };

const initDate = {
  submitResult: { type: 'idle', data: '' },
  formData: { uname: '', comment: '' },
};

// React 18实现(通过非受控来管理"表单")
export default function formDemo() {
  const [isPending, setIsPending] = useState(false);
  const [submitResult, setSubmitResult] = useState<SubmitResult>(_.cloneDeep(initDate.submitResult));


  // 提交操作
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault(); // 阻止默认行为(页面重定向)
    setIsPending(true);
    const formData = new FormData(e.target as HTMLFormElement); // 通过非受控模式获取表单数据
    const res = await api.submitComment(formData);
    setSubmitResult({ type: res.code === 0 ? 'success' : 'error', data: res.msg });
    setIsPending(false);
  };

  // 监听重置操作
  const onReset = () =>  setSubmitResult(_.cloneDeep(initDate.submitResult));

  return (
    <div className="w-auto">
      <form onSubmit={handleSubmit} className={cn('flex flex-col gap-2', { 'opacity-50': isPending })} onReset={onReset}>
        <div>
          <label>昵称:</label>
          <input name="uname"  className="border rounded px-2" />
        </div>
        <div>
          <label>评论:</label>
          <input name="comment"  className="border rounded px-2" />
        </div>

        <div className="flex gap-2">
          <button className="bg-blue-400 text-white px-2 rounded w-20 p-1" type='submit'>
            发表
          </button>
          <button className="bg-red-400 text-white px-2 rounded w-20 p-1" type='reset'>
            重置
          </button>
        </div>
      </form>

      <hr className="my-2" />

      {match(isPending)
        .with(true, () => <span className="text-yellow-400 text-xs">提交中...</span>)
        .otherwise(() =>
          match(submitResult.type)
            .with('error', () => <span className="text-red-400 text-xs">{submitResult.data}</span>)
            .with('success', () => <span className="text-green-400 text-xs">{submitResult.data}</span>)
            .otherwise(() => null),
        )}
    </div>
  );
}

react19非受控优化版

利用新的 hook useActionState,完美实现一个 state 都不需要定义

import _ from 'lodash';
import cn from '@/utils/cn';
import * as api from '../api';
import { match } from 'ts-pattern';
import { useActionState, startTransition } from 'react';

type SubmitResult = { type: 'idle' | 'success' | 'error'; data: string };

const initSubmitResult: SubmitResult = { type: 'idle', data: '' };

// React 19实现(通过useActionState结合非受控来管理"表单")
export default function formDemo() {
  const handleSubmit = async (_: SubmitResult, formData: FormData): Promise<SubmitResult> => {
    if (formData.has('reset')) return initSubmitResult;
    const res = await api.submitComment(formData);
    return { type: res.code === 0 ? 'success' : 'error', data: res.msg };
  };

  const [submitResult, formAction, isPending] = useActionState(handleSubmit, initSubmitResult);

  const onReset = () => {
    // 因为重置不会调用formAction,所以我们要手动调用,便于重置submitResult
    startTransition(() => {
      const formData = new FormData();
      formData.append('reset', '');
      formAction(formData);
    });
  };

  return (
    <div className="w-auto">
      <form action={formAction}  className={cn('flex flex-col gap-2', { 'opacity-50': isPending })} onReset={onReset}>
        <div>
          <label>昵称:</label>
          <input name="uname" className="border rounded px-2" />
        </div>
        <div>
          <label>评论:</label>
          <input name="comment" className="border rounded px-2" />
        </div>

        <div className="flex gap-2">
          <button className="bg-blue-400 text-white px-2 rounded w-20 p-1" type="submit">
            发表
          </button>
          <button className="bg-red-400 text-white px-2 rounded w-20 p-1" type="reset">
            重置
          </button>
        </div>
      </form>

      <hr className="my-2" />

      {match(isPending)
        .with(true, () => <span className="text-yellow-400 text-xs">提交中...</span>)
        .otherwise(() =>
          match(submitResult.type)
            .with('error', () => <span className="text-red-400 text-xs">{submitResult.data}</span>)
            .with('success', () => <span className="text-green-400 text-xs">{submitResult.data}</span>)
            .otherwise(() => null),
        )}
    </div>
  );
}

注意:useActionState的提交完成后会导致表单数据被重置,这是此钩子的默认行为,不习惯的可以参考这里

总结

该文通过三个代码示例,循序渐进地展示了在React中处理表单的三种不同模式,重点说明了从传统方式到React 19新特性的演进过程。

  1. 受控模式 (Controlled Component)

    • 核心思想:使用React的useState来全面管理表单的所有状态,包括每个输入框的值 (formData)、提交中的状态 (isPending) 和提交结果 (submitResult)。
    • 缺点:代码相对繁琐,尤其是当表单字段很多时,需要定义和维护大量的状态。
  2. 非受控模式 (Uncontrolled Component)

    • 核心思想:利用原生HTML的<form>特性,将表单数据交由DOM管理。React不再追踪每个输入框的值,只在提交时通过new FormData(formElement)一次性获取所有数据。
    • 优点:显著简化了代码,开发者只需关心提交中 (isPending) 和提交结果 (submitResult) 的状态,无需为每个字段创建state。
  3. React 19 非受控优化版 (Optimized with useActionState)

    • 核心思想:利用React 19 推出的新Hook useActionState 来进一步简化非受控表单。
    • 优点:达到了极致的简洁。useActionState 这一个Hook就统一管理了 提交结果、加载状态(isPending)、以及表单动作(action),完全无需手动创建任何useState。代码变得更加声明式,可读性更高。
    • 实现方式:将提交逻辑封装成一个action函数,并将其传递给useActionState,然后将返回的formAction绑定到<form>action属性上。

文件清晰地展示了React表单处理模式的演进:从需要大量手动状态管理的“受控模式”,到简化数据管理的“非受控模式”,最终到借助React 19 useActionState 实现几乎零手动状态管理的“优化非受控模式”,体现了React框架在提升开发效率和减少样板代码方面的持续努力。

posted @ 2025-11-13 13:59  丁少华  阅读(5)  评论(0)    收藏  举报