react(三)action 表单

本文是基于 react19 的表单教学

文章目录

  • 传统表单
  • [React 19 Action 表单](#React 19 Action 表单)
    • [基础用法 - 使用 action 属性](#基础用法 - 使用 action 属性)
    • 表单重置
    • [使用 useActionState 管理状态](#使用 useActionState 管理状态)
    • [使用 useFormStatus 获取表单状态](#使用 useFormStatus 获取表单状态)
    • [useFormStatus 和useActionState](#useFormStatus 和useActionState)
    • 表单防重复提交
    • [乐观更新(Optimistic Updates)](#乐观更新(Optimistic Updates))
    • [客户端 Action​ 和 服务端 Action​](#客户端 Action 和 服务端 Action)

传统表单

在 React 18 及之前,处理表单通常需要手动管理 useState、onChange、加载状态 (isPending)、错误处理,并编写 onSubmit 函数来阻止默认事件和发起异步请求。

js 复制代码
// 传统方式 - 手动管理一切
import { useState } from "react"

export const TraditionalForm = () => {
  // 1. 需要手动管理每个字段的状态
  const [formData, setFormData] = useState<{ name: string; email: string }>({
    name: '',
    email: ''
  });
  // 2. 需要手动管理加载状态
  const [loading, setLoading] = useState<boolean>(false);

  // 3. 需要手动管理错误
  const [error, setError] = useState(null);

  // 4. 手动处理输入变化
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  // 5. 手动处理提交
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    // 必须阻止默认行为
    e.preventDefault();
    setLoading(true);
    setError(null);
    console.log('提交的表单数据:', formData);
    try {
      // 模拟异步操作,例如发送请求
      setTimeout(() => {
        setLoading(false);
        console.log('表单提交成功!');
      }, 2000);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  }
  // 手动重置表单数据
  const reset = () => {
    setFormData({ name: '', email: '' });
    setError(null);
  }

  return (
    <div>
      <h2>传统表单</h2>
      <form onSubmit={handleSubmit}> 
        <div>
          <label htmlFor="name">姓名:</label>
          {/* 通过 onChange 事件更新 formData 的 name 属性 */}
          <input type="text" id="name" name="name" onChange={handleChange} />
        </div>
        <div>
          <label htmlFor="email">邮箱:</label>
          {/* 通过 onChange 事件更新 formData 的 email 属性 */}
          <input type="email" id="email" name="email" onChange={handleChange} required />
        </div>
        {error && <div style={{color: 'red'}}>{error}</div>}
        <button disabled={loading} type="submit">{loading ? '提交中...' : '提交'}</button>
        <button type="reset" onClick={reset}>重置</button>
      </form>
    </div>
  )
}

React 19 Action 表单

Actions 是使用异步转换的函数。你可以直接将异步函数传递给 的 action 属性,React 会自动管理表单提交的整个生命周期。

工作原理:

  • Action 函数接收 FormData 对象作为参数
  • 支持同步和异步函数- 表单提交时自动处理加载状态

基础用法 - 使用 action 属性

js 复制代码
// 模拟一个 POST 请求,实际项目中可以替换为 fetch 或 axios 等库
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
      resolve({ success: true });
    }, 2000);
  });
}
export const ActionFrom = () => {
  const submitForm = async (formData: FormData) => {
    try {
      // 直接提交,无需手动管理 loading 状态
      const response = await mockPostRequest(formData);
      if (response.success) {
        console.log('表单提交成功!');
        // 成功后可重置表单
      } else {
        console.error('表单提交失败!');
      }
    } catch (error) {
      console.error('提交表单时发生错误:', error);
    }
  }
  return (
    <div>
      <h2>ActionFrom</h2>
      {/* 直接使用 action 属性,不需要 onSubmit 和 preventDefault */}
      <form action={submitForm}>
        <div>
          <label htmlFor="name">姓名:</label>
           <input name="name" placeholder="姓名" />
        </div>
        <div>
          <label htmlFor="email">邮箱:</label>
          <input name="email" placeholder="邮箱" />
        </div>
      <button type="submit">提交</button>
      </form>
    </div>
  )
}

表单重置

  1. 自动重置(推荐)
    这是 React 19 的默认行为。当你给 form> 的 action 传入一个异步函数,并且该函数成功执行完成(没有抛出错误)后,React 会自动将表单内所有不受控组件(即没有显式设置 value,但有 name 属性的组件)的值重置为其初始状态 。
js 复制代码
// 自动重置:当 updateName 成功执行后,表单会自动清空
function MyForm() {
  const submitAction = async (formData) => {
    const name = formData.get('name');
    try {
      await updateName(name); // 如果请求成功,React 19 会自动重置表单
      alert('提交成功,表单已自动重置');
    } catch (error) {
      console.error('提交失败,表单保留原数据', error);
    }
  };

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit">提交</button>
    </form>
  );
}
  1. 手动重置
    如果你需要在特定时机手动重置表单(例如点击一个独立的"清空"按钮),可以使用新的 React DOM API requestFormReset 。
js 复制代码
import { requestFormReset } from 'react-dom';

function MyForm() {
  const formRef = useRef(null);

  const handleResetClick = () => {
    if (formRef.current) {
      requestFormReset(formRef.current); // 手动清空表单
    }
  };

  return (
    <form ref={formRef} action={submitAction}>
      {/* 表单内容 */}
      <button type="button" onClick={handleResetClick}>手动重置</button>
    </form>
  );
}

使用 useActionState 管理状态

useActionState 是 React 19 专门为简化表单和异步操作状态管理而推出的一个新 Hook。它可以帮你把之前分散在各个 useState 里的加载状态、错误信息和提交结果都整合起来,大大减少了样板代码。

侧重于表单提交结果的状态

基本语法

const [state, formAction, isPending] = useActionState(fn, initialState, permalink)

参数详解 : useActionState(fn, initialState, permalink)

  • fn:必填 (previousState, payload) => newState ;当表单提交时执行的函数(通常是异步的)。它的第一个参数会自动接收上一次调用后的状态,第二个参数是本次的表单数据(FormData 对象)
  • initialState: 必填;state 的初始值,可以是任何可序列化的值(如 null、{}、"")
  • permalink:选填;用于渐进式增强:如果在JS加载前就提交了表单,浏览器会导航到这个指定的URL,而不是当前页面的URL

返回值详解 : state, formAction, isPending

  • state :当前的状态值。首次渲染时等于你传入的 initialState,表单提交后会更新为 fn 函数返回的值
  • formAction:新的 action 函数。你需要把它赋给 标签的 action 属性
  • isPending:React 19 新增。表示表单是否正在提交中(pending),非常适合用来禁用按钮或显示加载动画,提升用户体验

使用场景

这个hook 非常适合处理那些需要根据后端返回结果来展示错误信息的表单,比如检查用户名是否已被占用、密码格式是否正确等。它让你能在一个地方统一管理提交逻辑和错误反馈。

js 复制代码
import { useActionState } from 'react';
// 模拟一个 POST 请求,实际项目中可以替换为 fetch 或 axios 等库
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
      resolve({ success: true });
    }, 2000);
  });
}

// 定义 state 的类型
interface FormState {
  success: boolean | null;
  message: string;
  errors: Record<string, string>;
}

export const UseActionStateForm = () => {
  const submitForm = async (prevState: FormState, formData: FormData) : Promise<FormState> => {
    // prevState: 上一次的状态
    // formData: 表单数据
    console.log('上一次的状态:', prevState);
    
    try {
      const response = await mockPostRequest(formData);
      if (response.success) {
        console.log('表单提交成功!');
        // 成功后可重置表单
      } else {
        console.error('表单提交失败!');
      }
      // 返回新的状态,useActionState 会自动更新 state
      return {
        success: true,
        message: '提交成功!',
        errors: {},
      };
    } catch (error) {
      console.error('提交表单时发生错误:', error);
      // 返回错误状态
      return {
        success: false,
        message: error instanceof Error ? error.message : '提交失败',
        errors: {},
      };
    }
    
  }
  
  // // useActionState 返回 [state, action, isPending]
  const [state, formAction, isPending] = useActionState(
      submitForm,
    { success: null, message: '', errors: {} } // 返回值 state 的初始值
  );
  return (
    <div>
      <h2>UseActionStateForm</h2>
      <form action={formAction}>
        <div>
          <label htmlFor="name">姓名:</label>
           <input name="name" placeholder="姓名" />
        </div>
        <div>
          <label htmlFor="email">邮箱:</label>
          <input name="email" placeholder="邮箱" />
        </div>
        <button type="submit" disabled={isPending}>
          {/* 显示提交状态 使用 isPending */}
          {isPending ? '提交中...' : '提交'}
        </button>
        <div>
          {/* 显示返回的消息 使用 state.message */}
          {state.message && <p>{state.message}</p>}
        </div>
      </form>
    </div>
  )
}

表单验证

使用 useActionState 整合验证和提交的流程。
注意:useActionState 返回的 state 类型由你决定,可以是字符串、对象等,初始值通过第二个参数传入 。

由于使用了 action 提交后验证失败会充值表单,无法保存填写过的数据,所以我们需要使用 useState 来保存表单数据

js 复制代码
import { useActionState, useState } from 'react';

// 模拟一个 POST 请求
function mockPostRequest(formData: FormData): Promise<{ success: boolean }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('模拟发送请求,表单数据:', Object.fromEntries(formData.entries()));
      resolve({ success: true });
    }, 2000);
  });
}

// 定义 state 的类型
interface FormState {
  success: boolean | null;
  message: string;
  errors: Record<string, string>;
}

export const UseActionStateForm = () => {
  // 使用 useState 来管理表单状态,初始值为 null 或者一个默认状态对象,防止验证失败重置
  const [formData, setFormData] = useState({
    name: '',
    email: ''
  });
  // 注意:这里使用 action 函数的形式
  const submitForm = async (
    prevState: FormState | null,
    formData: FormData
  ): Promise<FormState | null> => {
    console.log('上一次的状态:', prevState);

    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    
    // 简单的前端验证,返回的状态(验证结果)会自动更新到组件中
    if (!name || !email) {
      return {
        success: false,
        message: '请填写所有字段',
        errors: {
          name: !name ? '姓名不能为空' : '',
          email: !email ? '邮箱不能为空' : '',
        },
      };
    }
    
    try {
      await mockPostRequest(formData);
      console.log('表单提交成功!');
      return {
        success: true,
        message: '提交成功',
        errors: {}
      }; // 成功,返回成功状态
    } catch (error) {
      console.error('提交表单时发生错误:', error);
      // 返回错误状态
      return {
        success: false,
        message: '提交失败',
        errors: {}
      };
    }
  };
  
  // 使用 useActionState 来获取当前状态、提交函数和加载状态
  const [state, formAction, isPending] = useActionState(
    submitForm,
    null as FormState | null
  );

  // 处理输入变化,更新本地状态(可选)
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  }
  
  return (
    <div>
      <h2>UseActionStateForm</h2>
      <form action={formAction}>
        <div>
          <label htmlFor="name">姓名:</label>
          <input name="name" placeholder="姓名" value={formData.name}
            onChange={handleInputChange} />
        </div>
        <div>
          <label htmlFor="email">邮箱:</label>
          <input name="email" placeholder="邮箱" value={formData.email}
            onChange={handleInputChange} />
        </div>
        <button type="submit" disabled={isPending}>
          {isPending ? '提交中...' : '提交'}
        </button>
        <div>
          {/* 显示返回状态------验证错误信息 */}
          {state?.errors?.name && (
            <p style={{ color: 'red' }}>{state.errors.name}</p>
          )}
          {state?.errors?.email && (
            <p style={{ color: 'red' }}>{state.errors.email}</p>
          )}
          {/* 显示返回状态------通用消息 */}
          {state?.message && (
            <p style={{ 
              color: state.success ? 'green' : 'red',
              marginTop: '10px'
            }}>
              {state.message}
            </p>
          )}
        </div>
      </form>
    </div>
  );
};

使用 useFormStatus 获取表单状态

useFormStatus 是 React 19 提供的一个新 Hook,用于获取表单提交时的状态信息。它可以帮助你实现提交时禁用按钮、显示加载状态、展示正在提交的数据等交互。

侧重于表单提交过程的信息

返回值

它不接收任何参数,返回一个包含以下四个属性的对象:

属性 类型 说明
pending boolean true 表示表单正在提交中,false 表示空闲
data FormData 对象或 null 包含父级 正在提交的数据。若没有正在进行的提交,则为 null。可以通过 data.get(fieldName) 获取具体字段值
method string 表单的提交方法,值为 'get' 或 'post',对应 标签的 method 属性
action function 或 null 指向父级 的 action 属性的函数引用。如果 action 是一个 URL 字符串或未指定,则该值为 null。

使用规则

  • <form> 表单里面使用。
  • 需要配合 action 属性使用
  • 只获取离他最近的父级 的状态。
  • useFormStatus 必须在 <form> 的子组件中调用
  • useFormStatus 是从 'react-dom' 包中导出的,而不是 'react'。import { useFormStatus } from 'react-dom';

子组件

js 复制代码
// statusMessage.tsx
import { useFormStatus } from 'react-dom';

export const StatusMessage = () => {
  const { pending, data } = useFormStatus();
  if (pending && data) {
    // 通过 data.get('字段名') 读取正在提交的数据
    const username: string = data.get('username') as string;
    return <p>正在为 <strong>{username}</strong> 提交申请...</p>;
  }
  return <p>请填写并提交表单。</p>;
}

父组件

js 复制代码
import { StatusMessage } from './statusMessage';

export const useFormStatusForm = () => { 
  const formAction = async (formData: FormData) => {
    console.log('提交的表单数据:', Object.fromEntries(formData.entries()));
    // 模拟异步操作,例如发送请求
    await new Promise((resolve) => {
      setTimeout(() => {
        console.log('表单提交成功!');
        resolve(null);
      }, 2000);
    });
  }
  return (
    <div>
      <h2>UseFormStatusForm</h2>
      {/* useFormStatus 要配合 action 使用 */}
      <form action={formAction}>
        <div>
          <label htmlFor="name">姓名:</label>
          <input name="username" type="text" />
        </div>
        <button type="submit">
          提交
        </button>
        {/* 在表单里使用,只能获取最近父级form的状态 */}
        <StatusMessage /> {/* 状态消息会随提交过程更新 */}
      </form>
    </div>
  )
}

useFormStatus 和useActionState

useFormStatus 和 useActionState 都是 React 19 中与表单相关的 Hook,但它们的关注点和使用场景完全不同。

简单来说:

  • useFormStatus:关注"提交过程的状态"(正在提交吗?提交的是什么数据?)
  • useActionState:关注"提交结果的状态"(提交成功了吗?返回了什么数据?有没有错误?)

不同点

  1. 关注点不同
    useFormStatus - 关注"过程"
js 复制代码
// 问:表单正在提交吗?提交的用户名是什么?
function SubmitButton() {
  const { pending, data } = useFormStatus();
  const username = data?.get('username');
  
  return (
    <button disabled={pending}>
      {pending ? `正在为 ${username} 提交...` : '提交'}
    </button>
  );
}

useActionState - 关注"结果"

js 复制代码
// 问:上次提交成功了吗?服务器返回了什么消息?
function MyForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      // 执行提交逻辑
      const result = await submitToAPI(formData);
      // 返回的结果会成为新的 state
      return { success: true, message: '提交成功!' };
    },
    { success: false, message: '' } // 初始状态
  );

  return (
    <form action={formAction}>
      <input name="email" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '提交'}
      </button>
      {/* 显示提交结果 */}
      {state.message && <p>{state.message}</p>}
    </form>
  );
}
  1. 使用位置不同
    useFormStatus:useFormStatus:必须在子组件中
js 复制代码
// 正确:在子组件中使用
function Form() {
  return (
    <form action={submitAction}>
      <SubmitButton />  {/* useFormStatus 在这里 */}
    </form>
  );
}

// 错误:在表单组件本身使用
function Form() {
  const { pending } = useFormStatus(); // pending 永远是 false!
  return <form action={submitAction}>...</form>;
}

useActionState:通常在表单组件本身

js 复制代码
// 在表单组件中使用
function Form() {
  const [state, formAction] = useActionState(action, initialState);
  return <form action={formAction}>...</form>;
}
  1. 访问的数据类型不同
    useFormStatus - 访问原始表单数据
js 复制代码
function StatusReader() {
  const { data } = useFormStatus();
  // data 是 FormData 对象,包含用户输入的原始值
  const password = data?.get('password'); // 原始字符串
  const file = data?.get('avatar');       // File 对象
}

useActionState - 访问处理后的结果

js 复制代码
const [state, formAction] = useActionState(async (prev, formData) => {
  const user = await createUser(formData);
  // state 可以是任何类型:对象、数组、字符串等
  return { userId: user.id, message: '创建成功' };
}, null);

// 使用 state
console.log(state?.userId);   // 业务数据
console.log(state?.message);  // 提示信息

组合使用:两者并不互斥

它们可以同时使用,解决不同层面的问题:

js 复制代码
// 主表单组件:使用 useActionState 管理结果
function FeedbackForm() {
  const [state, formAction] = useActionState(submitFeedback, null);
  
  return (
    <form action={formAction}>
      <textarea name="feedback" />
      
      {/* 子组件:使用 useFormStatus 管理提交过程 */}
      <SubmitButton />
      
      {state?.success && <p>感谢反馈!</p>}
      {state?.error && <p>提交失败,请重试</p>}
    </form>
  );
}

// 子组件:只关心提交过程
function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '发送中...' : '发送反馈'}
    </button>
  );
}

表单防重复提交

在上面的useActionState中已经有 isPending 可以自动管理 pending 状态。下面我们再学习另一个防止重复提交的hook。

useTransition 是 React 官方提供的标准 Hook,用于处理并发渲染场景下的非紧急状态更新。

useTransition 返回一个包含两个元素的数组,用于管理非紧急状态更新的过渡效果,特别适合处理表单提交等异步操作。

  • isPending: boolean - 表示过渡是否正在进行中
  • startTransition: function - 用于将状态更新标记为过渡的函数
js 复制代码
import { useTransition, useEffect } from "react";

export const UseTransitionForm = () => {
  // 防止重复提交:isPending 为 true 时不允许新提交
  const [isPending, startTransition] = useTransition();

  const handleSubmit = (formData: FormData) => {
    console.log('1. 函数开始执行, isPending:', isPending); // false
    // 这里的更新会被标记为"过渡"
    // 可以包含同步或异步操作
    startTransition(async () => {
      console.log('2. 进入 startTransition 回调');
      console.log('提交的表单数据:', Object.fromEntries(formData.entries()));
      try {
        // 模拟 API 调用
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log('提交完成');
      } catch (error) {
        console.error('提交失败', error);
      }
    });
    // 注意:startTransition 调用后,isPending 仍然是 false,直到下一个渲染周期才会更新
    console.log('4. startTransition 调用完成, isPending:', isPending); // 仍然是 false
  };

  // 在下一次渲染时,isPending 会变为 true
  useEffect(() => {
    console.log('组件重新渲染, isPending:', isPending);
  });

  return (
    <div>
      <h2>useTransition 表单</h2>
      <form action={handleSubmit}>
      <input name="name" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '提交'}
      </button>
      </form>
    </div>
  )
}


总结:

  • 提交后立即禁用按钮(下一个渲染周期)
  • 请求期间用户无法重复提交
  • 请求完成后自动恢复

乐观更新(Optimistic Updates)

乐观更新的核心思路:用户提交操作时,不等服务器响应,先假设操作会成功,立即在界面上看到结果。等服务器返回结果后,要么确认更新,要么把界面回滚。

三步走:

  1. 立即更新:用户操作 → 界面马上改变(假设成功)
  2. 后台请求:同时发送真实的 API 请求
  3. 确认或回滚:
    • 成功 → 什么都不用做(已经是正确的了)
    • 失败 → 把界面改回原样,提示用户

乐观更新模式 VS 传统模式


useOptimistic 是 React 19 专为乐观更新设计的 Hook,使用方式如下

js 复制代码
const [optimisticState, addOptimistic] = useOptimistic(
  state,           // 基础状态(真实数据的来源)
  updateFunction   // 可选:定义如何计算乐观状态
);
  • 参数
参数 类型 说明 必填
state T 基础状态值,通常是真实数据(来自 useState、props、store 等)
updateFunction (currentState: T, optimisticValue: P) => T 接收当前状态和乐观值,返回新的乐观状态
  • 返回值
返回值 类型 说明
optimisticState T 当前显示的乐观状态,可能是真实状态或临时乐观状态
addOptimistic (value: P) => void 调用此函数会立即触发乐观更新,React 会重新渲染组件

关键点:

  • addOptimistic 调用后立即同步更新(在当前事件循环中)
  • addOptimistic 不返回 Promise,纯粹是 UI 层面的更新
  • 乐观更新是暂时性的,等待真实数据返回后会覆盖
js 复制代码
import { useOptimistic } from "react";

const mockPostRequest = (data: { message: string }): Promise<{ success: boolean }> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('模拟发送请求,表单数据:', data);
      resolve({ success: true });
    }, 1000);
  });
}

export const UseOptimisticForm = () => {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<
    { text: string, sending: boolean }[],
    string
    >(
    // 初始状态,可以是一个空数组或者预加载的消息列表
    [],
    // 定义如何添加一个新的乐观消息
    (state, newMessage) => [...state, { text: newMessage, sending: true }]
  );

  const handleSubmit = async (formData: FormData) => {
    const message = formData.get('message') as string;
    if (!message.trim()) return;
    // 1. 立即添加一个乐观消息
    addOptimisticMessage(message); // 同步执行
    // 显示乐观消息。
    // React 会在当前事件循环结束时重新渲染
    try {
      // 2. 模拟发送请求。
      await mockPostRequest({ message });
      // 成功:保持消息,移除"正在发送"标记
    } catch (error) {
      // 失败:乐观更新失败,需要移除这条消息
      console.error('发送失败:', error);
      // 可以在这里删除乐观添加的消息
    }
  }

  return (
    <>
      <h2>乐观更新表单</h2>
      <form action={handleSubmit}>
        <input type="text" name="message" />
        <button type="submit">发送</button>
      </form>
      <ul>
        {optimisticMessages.map((msg, index) => (
          <li key={index} style={{ color: msg.sending ? 'gray' : 'black' }}>
            {/* 显示消息内容 */}
            {msg.text} {msg.sending && '(正在发送...)'}
          </li>
        ))}
      </ul>
    </>
  );
}

记住核心:useOptimistic 只负责 UI 的即时反馈,真正的数据同步需要你自己处理!

客户端 Action​ 和 服务端 Action​

在 React 应用中,客户端 Action​ 和 服务端 Action​ 是两种不同的处理异步操作的方式。

什么是客户端 Action?

在客户端(浏览器)执行的异步函数,通常处理不需要服务端参与的 UI 逻辑。

  • 语法:在文件顶部声明 'use client',标记该文件及其导入的模块为 客户端组件。
  • 代码会在浏览器中执行,可以访问 window、document等浏览器 API,以及 React 状态、事件、hooks。
  • 通常用于交互性强的 UI 部分。

什么是服务端 Action?

在服务端执行的异步函数,通过 'use server'标记,可以安全地处理敏感操作。

  • 语法:在文件顶部声明 'use server',标记该函数为 服务端函数。
  • 该函数仅在服务端执行,不会将实现逻辑发送到客户端。
  • 用于安全地执行数据库操作、API 调用等敏感逻辑。
特性 客户端 Action 服务端 Action
执行环境 浏览器 (客户端)
指令标识 无需特殊指令或在文件顶部声明 'use client' 'use server'
典型场景 调用第三方 API、本地缓存、UI 状态更新 数据库操作、文件系统访问、敏感逻辑
代码体积 会打包到客户端 bundle 不会发送到客户端,仅保留引用
与表单集成 通过 action 属性 通过 action 属性
需要 API 路由 不需要(直接调用) 不需要(框架自动处理)

客户端 Action

js 复制代码
'use client';

async function createUserAction(formData) {
  // 直接在客户端执行
  const name = formData.get('name');
  
  const res = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify({ name }),
  });
  
  if (!res.ok) throw new Error('创建失败');
  return { success: true };
}

function UserForm() {
  return (
    <form action={createUserAction}>
      <input name="name" />
      <button type="submit">提交</button>
    </form>
  );
}

服务端 Action

  1. 在 Server Component 中定义
js 复制代码
// Server Component
import Button from './Button';

function EmptyNote() {
  async function createNoteAction() {
    'use server';  // 标记为服务端 Action
    
    await db.notes.create();  // 直接操作数据库
  }
  
  return <Button onClick={createNoteAction} />;
}
js 复制代码
// Client Component (Button)
'use client';

export default function Button({ onClick }) {
  // onClick 是一个引用,不是函数体
  console.log(onClick); 
  // { $$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction' }
  
  return <button onClick={() => onClick()}>创建笔记</button>;
}
  1. 从独立文件导入
js 复制代码
// actions.ts
'use server';

export async function updateName(name: string) {
  if (!name) {
    return { error: '姓名不能为空' };
  }
  await db.users.updateName(name);
  return { success: true };
}
js 复制代码
// Client Component
'use client';
import { updateName } from './actions';

function UpdateName() {
  return (
    <form action={updateName}>
      <input name="name" />
      <button type="submit">更新</button>
    </form>
  );
}

与表单 Action 的集成

服务端 Action 可以与表单的 action 属性无缝集成:

js 复制代码
'use client';
import { updateName } from './actions';

function UpdateName() {
  return (
    <form action={updateName}>
      <input name="name" />
      <button type="submit">更新</button>
    </form>
  );
}

当表单提交时:

  1. React 将表单数据序列化
  2. 发送 HTTP 请求到服务器
  3. 服务器执行 updateName 函数
  4. 结果返回客户端,表单自动重置

配合 useActionState 使用(推荐)

无论客户端还是服务端 Action,都可以配合 useActionState 来管理状态和加载状态:

js 复制代码
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';  // 服务端 Action

function UpdateName() {
  const [state, submitAction, isPending] = useActionState(
    updateName,           // 可以是客户端或服务端 Action
    { error: null }      // 初始状态
  );

  return (
    <form action={submitAction}>
      <input name="name" disabled={isPending} />
      {state.error && <span>{state.error}</span>}
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '更新'}
      </button>
    </form>
  );
}

注意:useFormState 在 React 19 中已重命名为 useActionState,并从 react-dom 移至 react。

渐进增强

服务端 Action 配合 useActionState 的第三个参数 permalink,可以实现渐进增强------即使用户的 JavaScript 还未加载完成,表单依然可以正常提交:

js 复制代码
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';

function UpdateName() {
  const [, submitAction] = useActionState(
    updateName, 
    null,
    '/name/update'  // permalink:JS 未加载时 fallback 到此 URL
  );

  return (
    <form action={submitAction}>
      <input name="name" />
      <button type="submit">更新</button>
    </form>
  );
}

总结:

  • 客户端 Action:直接执行,适合调用外部 API
  • 服务端 Action:通过 'use server' 标记,代码留在服务器,适合数据库等敏感操作
  • 统一的使用方式:都可以传给 ,配合 useActionState 和 useFormStatus 管理状态
  • 不需要手动写 API 路由:框架自动处理服务端 Action 的网络通信
相关推荐
我命由我123452 小时前
在 React 项目中,可以执行 npm start 命令,但是,无法执行 npm build 命令
前端·javascript·vue.js·react.js·前端框架·json·ecmascript
程序员Forlan2 小时前
fiddler+手机或模拟器进行APP抓包
前端·智能手机·fiddler
aidou13142 小时前
Vue3自定义实现日期选择器(可单选或多选)
前端·javascript·vue.js·日期选择器·transition
绝世唐门三哥2 小时前
OpenClaw 安装 + 手动配置 DeepSeek 模型(2026.4.5 版)
前端·oepn claw
来一颗砂糖橘2 小时前
吃透 ES6 扩展运算符(...):从表面语法到底层逻辑,避开所有坑
前端·javascript·es6·扩展运算符·前端进阶
前端小D2 小时前
JS模块化
开发语言·前端·javascript
Muen2 小时前
iOS开发-适配XCode26、iOS26
前端
ByteCraze2 小时前
JavaScript 深拷贝完全指南:从入门到精通
开发语言·javascript·ecmascript
用户84298142418102 小时前
3个Html加密工具
javascript