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

Svelte 5表单库:如何从子组件推断FormData类型?

实现Svelte 5表单库的类型安全与自动推断

完全可行,借助Svelte 5的泛型组件和插槽类型推断能力,你可以从子组件Input的key和type属性自动推导表单提交数据的具体类型,实现完整的TypeScript类型安全和VSCode自动补全。

核心实现步骤

1. 定义Input组件的类型映射

先给Input组件建立type属性到TS类型的映射关系,让不同的输入类型对应明确的TS类型:

<!-- Input.svelte -->
<script lang="ts">
// 映射input类型到对应的TS类型
type InputTypeMap = {
  text: string;
  email: string;
  password: string;
  number: number;
  checkbox: boolean;
  date: Date;
  // 可根据需求扩展更多类型
};

// Input组件的Props类型,绑定key和type的关联关系
export type InputProps<K extends string, T extends keyof InputTypeMap> = {
  key: K;
  label: string;
  icon?: string;
  type: T;
  // 可添加其他props如required、placeholder等
};

// 声明组件props,使用泛型约束
export let key: K;
export let label: string;
export let icon: string | undefined = undefined;
export let type: T = "text" as T;
</script>

<!-- 组件DOM结构示例 -->
<div class="input-field">
  {#if icon}
    <span class="icon">{icon}</span>
  {/if}
  <label for={key}>{label}</label>
  <input id={key} type={type} data-form-key={key} />
</div>

2. 实现Form组件的类型推断逻辑

在Form组件中,利用Svelte 5的$$Slots获取插槽内Input组件的props类型,自动推导表单数据的结构:

<!-- Form.svelte -->
<script lang="ts">
import type { InputProps, InputTypeMap } from "./Input.svelte";

// 从Input组件的Props集合中提取表单数据类型
type ExtractFormData<Inputs extends InputProps<any, any>[]> = 
  Inputs[number] extends InputProps<infer K, infer T>
    ? { [Key in K]: InputTypeMap[T] }
    : never;

// Form组件的泛型Props,默认从插槽推断类型
export type FormProps<Data extends Record<string, any> = ExtractFormData<typeof $$Slots.default[0]>> = {
  onapprove: (data: Data) => Promise<void> | void;
};

export let onapprove: FormProps<Data>["onapprove"];

// 你的后台Form类,添加泛型支持
class FormInstance<Data> {
  private formData: Partial<Data> = {};

  // 模拟收集表单数据的逻辑(实际可通过DOM查询或双向绑定实现)
  private collectData(): Data {
    // 示例:遍历所有带data-form-key的input元素
    const inputs = document.querySelectorAll("[data-form-key]");
    inputs.forEach(input => {
      const key = input.getAttribute("data-form-key") as keyof Data;
      const type = input.type as keyof InputTypeMap;
      
      // 根据input类型转换值
      switch (type) {
        case "number":
          this.formData[key] = Number(input.value) as Data[keyof Data];
          break;
        case "checkbox":
          this.formData[key] = (input as HTMLInputElement).checked as Data[keyof Data];
          break;
        case "date":
          this.formData[key] = new Date(input.value) as Data[keyof Data];
          break;
        default:
          this.formData[key] = input.value as Data[keyof Data];
      }
    });
    return this.formData as Data;
  }

  // 提交方法,验证通过后触发onapprove
  async submit() {
    // 模拟验证逻辑
    const isValid = true;
    if (isValid) {
      const data = this.collectData();
      await onapprove(data);
    }
  }
}

// 导出form实例,绑定给父组件
export let form: FormInstance<Data> = new FormInstance<Data>();
</script>

<form onsubmit|preventDefault={() => form.submit()}>
  <slot />
</form>

3. 使用示例(自动推断类型)

在使用时,无需手动指定类型,TS会自动从Input的key和type推导handleApprove中data的类型:

<!-- LoginForm.svelte -->
<script lang="ts">
import Form from "./Form.svelte";
import Input from "./Input.svelte";

const handleApprove = async (data) => {
  // 这里data会被自动推断为:{ email: string; password: string }
  console.log(data.email); // VSCode会自动补全
  console.log(data.password);
  // 如果访问不存在的键(如data.username),TS会直接报错
};
</script>

<Form bind:form onapprove={handleApprove}>
  <Input key="email" label="邮箱" icon="mail" type="email" />
  <Input key="password" label="密码" icon="key" type="password" />
  <button onclick={() => form.submit()}>登录</button>
</Form>

4. 动态表单场景的显式类型指定

如果表单是动态生成的(比如循环渲染Input),无法通过插槽自动推断时,可以显式给Form组件指定泛型:

<script lang="ts">
import Form from "./Form.svelte";
import Input from "./Input.svelte";

// 显式定义表单数据类型
type UserFormData = {
  username: string;
  age: number;
  agree: boolean;
};

const handleApprove = async (data: UserFormData) => {
  // 类型完全匹配
};
</script>

<Form<UserFormData> bind:form onapprove={handleApprove}>
  {#each Object.entries(formFields) as [key, field]}
    <Input {key} label={field.label} type={field.type} />
  {/each}
  <button onclick={() => form.submit()}>提交</button>
</Form>

内容的提问来源于stack exchange,提问作者Hovadko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:19:58