如何在SvelteKit中实现可复用表单组件并响应式返回FormData
问题概述
站点存在数百个高度相似的页面,每个页面顶部英雄区都包含重复的表单代码及对应的+server.js文件。现将二者重构为组件以实现全局同步修改,但重构后出现以下问题:
- 表单验证通过后能正常发送邮件,但无法显示成功提示消息
- 表单填写错误导致验证失败时,无法显示错误提示消息,核心原因是FormData未正确返回至页面
现有代码
共享的server逻辑文件
import { z } from 'zod'; const contactSchema = z.object({ fullName: z .string({ required_error: 'Name is required' }) .min(2, { message: 'Please provide full name' }) .max(64, { message: 'Name must be less than 64 characters' }) .trim(), email: z .string({ required_error: 'Email is required' }) .trim() .max(64, { message: 'Name must be less than 64 characters' }) .email({ message: 'Please provide a valid Email' }), phone: z .string({ required_error: 'Phone is required' }) .min(10, { message: 'Please provide a valid phone number' }) .trim(), address: z .string({ required_error: 'Address is required' }) .min(5, { message: 'Please provide a valid address' }) .trim() }); export const actions = { contactus: async ({ request }) => { const formData = Object.fromEntries(await request.formData()); try { const result = contactSchema.parse(formData); sendMail(formData); return { success: true }; } catch (err) { const { fieldErrors: errors } = err.flatten(); return { formData, errors }; } } }; import sgMail from '@sendgrid/mail'; sgMail.setApiKey(import.meta.env.SENDGRID_API_KEY); export async function sendMail(formData) { const msgBodyHTML = 'Name: '.concat( formData.fullName, '<br/>Phone: ', formData.phone, '<br/>Email: ', formData.email, '<br/>Address: ', formData.address ); const msg = { to: ['admin@-----.ca'], from: 'noreply@-----.ca', subject: 'New Form', html: msgBodyHTML }; await sgMail.send(msg); }
Form组件(Svelte)
<script> export let form; </script> <!--Form--> <div> {#if form?.success} <h3 class="mb-4 text-center text-xl font-bold tracking-tight text-mainbg md:text-3xl xl:text-4xl" > Thank you! We will be in touch shortly! </h3> {:else} <h3 class="mb-4 text-center text-xl font-bold tracking-tight text-mainbg md:text-3xl xl:text-4xl" > Contact us Now </h3> <form action="?/contactus" method="POST" class="flex flex-col"> <div class="grid grid-cols-1 gap-3 px-6 md:grid-cols-2 md:gap-4"> <div class="flex flex-col justify-start text-xl"> {#if form?.formData?.fullName} <input id="fullName" name="fullName" class=" rounded-xl pl-2" type="text" placeholder="Name" value={form?.formData?.fullName} /> {:else} <input id="fullName" name="fullName" class=" rounded-xl pl-2" type="text" placeholder="Name" value="" />{/if} {#if form?.errors?.fullName} <span class="px-2 text-xs font-bold text-orangeAccent" >{form?.errors?.fullName[0]}</span > {/if} </div> <div class="flex flex-col justify-start text-xl"> {#if form?.formData?.email} <input id="email" name="email" class=" rounded-xl pl-2" type="email" placeholder="Email" value={form?.formData?.email} /> {:else} <input id="email" name="email" class=" rounded-xl pl-2" type="email" placeholder="Email" value="" /> {/if} {#if form?.errors?.email} <span class="px-2 text-xs font-bold text-orangeAccent" >{form?.errors?.email[0]}</span > {/if} </div> <div class="flex flex-col justify-start text-xl"> {#if form?.formData?.phone} <input id="phone" name="phone" class=" rounded-xl pl-2" type="text" placeholder="Phone" value={form?.formData?.phone} /> {:else} <input id="phone" name="phone" class=" rounded-xl pl-2" type="text" placeholder="Phone" value="" /> {/if} {#if form?.errors?.phone} <span class="px-2 text-xs font-bold text-orangeAccent" >{form?.errors?.phone[0]}</span > {/if} </div> <div class="flex flex-col justify-start text-xl"> {#if form?.formData?.address} <input id="address" name="address" class=" rounded-xl pl-2" type="text" placeholder="Address" value={form?.formData?.address} /> {:else} <input id="address" name="address" class=" rounded-xl pl-2" type="text" placeholder="Address" value="" /> {/if} {#if form?.errors?.address} <span class="px-2 text-xs font-bold text-orangeAccent" >{form?.errors?.address[0]}</span > {/if} </div> </div> <button class="mt-4 w-3/4 self-center rounded-2xl bg-orangeAccent py-1 font-bold shadow-lg hover:brightness-75" >Contact now!</button > </form> {/if} </div>
修复步骤
1. 修正服务器端响应逻辑
- 异步邮件发送需添加
await,确保邮件发送完成后再返回响应 - 成功响应中返回
formData(可选,用于保留用户输入内容) - 增加异常类型判断,避免非Zod错误导致程序崩溃
修改后的contactus函数:
contactus: async ({ request }) => { const formData = Object.fromEntries(await request.formData()); try { const result = contactSchema.parse(formData); await sendMail(formData); // 等待邮件发送完成 return { success: true, formData // 返回表单数据,便于组件显示历史输入 }; } catch (err) { if (err instanceof z.ZodError) { const { fieldErrors: errors } = err.flatten(); return { formData, errors }; } // 处理其他意外错误 return { formData, errors: { general: ['An unexpected error occurred'] } }; } }
2. 页面中正确传递表单状态
在使用Form组件的页面(如+page.svelte),需将页面获取的表单状态传递给组件:
<script> export let data; </script> <Form form={data.form} />
对应的页面服务器文件(+page.server.js)导入共享的actions逻辑,避免重复代码:
import { actions as contactActions } from '$lib/server/contact-server.js'; // 假设共享逻辑存放在此路径 export const actions = { contactus: contactActions.contactus };
3. 简化组件输入值判断逻辑
将组件中冗余的if/else判断替换为可选链+默认值,简化代码:
<!-- 以fullName输入框为例,其他输入框同理 --> <input id="fullName" name="fullName" class="rounded-xl pl-2" type="text" placeholder="Name" value={form?.formData?.fullName ?? ''} />
内容的提问来源于stack exchange,提问作者atseniuk
相关产品推荐
相关产品推荐

