You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 01:25:54