Next.js 14 Server Actions如何触发客户端回调?无需useEffect方案
在Next.js 14 Server Actions中实现回调式状态处理
针对你想要替代useFormState+useEffect的优化需求,以下是几种更简洁、复用性更强的方案:
方案1:封装自定义Hook复用回调逻辑
把状态处理和回调逻辑封装成可复用的自定义Hook,避免重复编写useEffect,同时让组件代码更简洁。
步骤1:定义结构化返回的Server Action
// app/actions.ts 'use server'; export async function registerUser(formData: FormData) { const email = formData.get('email') as string; // 模拟邮箱已存在的场景 if (email === 'existing@example.com') { return { success: false, message: '该邮箱已被注册', errorCode: 'EMAIL_EXISTS' }; } // 正常注册逻辑(省略数据库操作) return { success: true, message: '注册成功', data: { email } }; }
步骤2:编写自定义Hook
// app/hooks/useFormActionWithCallbacks.ts 'use client'; import { useFormState, useEffect } from 'react'; type ActionState<T = unknown> = { success: boolean; message: string; errorCode?: string; data?: T; }; export function useFormActionWithCallbacks<T>( action: (...args: any[]) => Promise<ActionState<T>>, initialState: ActionState<T>, callbacks: { onSuccess?: (data: T, message: string) => void; onError?: (errorCode: string, message: string) => void; } ) { const [state, formAction] = useFormState(action, initialState); useEffect(() => { // 初始状态不触发回调 if (state === initialState) return; if (state.success) { callbacks.onSuccess?.(state.data!, state.message); } else if (state.errorCode) { callbacks.onError?.(state.errorCode, state.message); } }, [state, initialState, callbacks]); return [state, formAction] as const; }
步骤3:在组件中使用
// app/register/page.tsx 'use client'; import { registerUser } from '../actions'; import { useFormActionWithCallbacks } from '../hooks/useFormActionWithCallbacks'; import { toast } from 'your-toast-library'; // 替换为你使用的Toast库 const initialState = { success: false, message: '', errorCode: undefined, data: undefined }; export default function RegisterPage() { const [state, formAction] = useFormActionWithCallbacks( registerUser, initialState, { onSuccess: (data, message) => { toast.success(message); // 注册成功后的额外操作,比如跳转首页 window.location.href = '/'; }, onError: (errorCode, message) => { if (errorCode === 'EMAIL_EXISTS') { toast.error(message); } } } ); return ( <form action={formAction}> <label htmlFor="email">邮箱</label> <input type="email" id="email" name="email" required /> <button type="submit">注册</button> </form> ); }
方案2:组件内精准依赖的useEffect优化
如果不想额外编写自定义Hook,可以通过缩小useEffect的依赖范围,避免不必要的重触发:
'use client'; import { useFormState, useEffect } from 'react'; import { registerUser } from '../actions'; import { toast } from 'your-toast-library'; const initialState = { success: false, message: '', errorCode: undefined }; export default function RegisterPage() { const [state, formAction] = useFormState(registerUser, initialState); useEffect(() => { // 跳过初始渲染 if (state === initialState) return; if (state.success) { toast.success(state.message); } else if (state.errorCode) { toast.error(state.message); } // 可选:重置状态,避免页面重新渲染时重复触发回调 // setState(initialState); }, [state.success, state.errorCode, state.message]); return ( <form action={formAction}> <label htmlFor="email">邮箱</label> <input type="email" id="email" name="email" required /> <button type="submit">注册</button> </form> ); }
方案3:结合useFormStatus处理加载+回调
如果需要同时管理提交加载状态,可以把useFormStatus和回调逻辑分离,让组件职责更清晰:
'use client'; import { useFormState, useFormStatus, useEffect } from 'react'; import { registerUser } from '../actions'; import { toast } from 'your-toast-library'; const initialState = { success: false, message: '', errorCode: undefined }; // 独立的提交按钮组件,处理加载状态 function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? '注册中...' : '注册'} </button> ); } export default function RegisterPage() { const [state, formAction] = useFormState(registerUser, initialState); useEffect(() => { if (state === initialState) return; if (state.success) { toast.success(state.message); } else if (state.errorCode) { toast.error(state.message); } }, [state]); return ( <form action={formAction}> <label htmlFor="email">邮箱</label> <input type="email" id="email" name="email" required /> <SubmitButton /> </form> ); }
内容的提问来源于stack exchange,提问作者OlusanyaAyomide
相关产品推荐
相关产品推荐

