模块化表单

模块化表单 是一个基于 Qwik 原生构建的类型安全表单库。无头设计让您可以完全控制表单的视觉外观。该库负责状态管理和输入验证。

要开始使用,请安装 npm 包

npm install @modular-forms/qwik

定义您的表单

在开始创建表单之前,您需要定义字段的结构和数据类型。除了字符串之外,模块化表单还可以处理布尔值、数字、文件、日期、对象和数组。

type LoginForm = {
  email: string;
  password: string;
};

由于模块化表单支持 ValibotZod 进行输入验证,因此您可以选择从模式中推导出类型定义。

import * as v from 'valibot';
 
const LoginSchema = v.object({
  email: v.pipe(
    v.string(),
    v.nonEmpty('Please enter your email.'),
    v.email('The email address is badly formatted.'),
  ),
  password: v.pipe(
    v.string(),
    v.nonEmpty('Please enter your password.'),
    v.minLength(8, 'Your password must have 8 characters or more.'),
  ),
});
 
type LoginForm = v.InferInput<typeof LoginSchema>;

如果您想知道为什么本指南更倾向于使用 Valibot 而不是 Zod,我建议您阅读这篇 公告文章

设置初始值

创建类型定义后,继续设置表单的初始值。为此,请创建一个 routeLoader$ 并使用您之前创建的类型作为泛型。

export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
  email: '',
  password: '',
}));

routeLoader$ 中,您可以使用空字符串,也可以查询并传递来自数据库的值。根据传递的对象,表单的存储将被初始化,使 Qwik 能够可靠地在服务器上预渲染您的网站。初始值也用于稍后检查用户输入后字段的值是否已更改。

创建表单

要创建表单,请使用 useForm 钩子。它返回表单的存储和一个包含 FormFieldFieldArray 组件的对象。作为参数,您将包含之前创建的加载器的对象传递给 useForm

export default component$(() => {
  const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
    loader: useFormLoader(),
  });
});

您可以使用 loginForm 对象访问表单的当前状态。此外,您可以将其传递给库提供的各种方法,例如 resetsetValue,以对状态进行手动更改。

在组件的 JSX 部分,您继续使用 Form 组件。它包含表单的字段,并且您可以通过其属性定义表单提交时会发生什么。

export default component$(() => {
  const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
    loader: useFormLoader(),
  });
 
  return <Form></Form>;
});

添加表单字段

现在您可以继续添加表单的字段。使用 FieldFieldArray 组件,您可以注册一个字段或字段数组。这两个组件都是无头的,并为您提供对其当前状态的直接访问权限。渲染道具的第二个参数必须传递给 <input /><select /><textarea /> 元素,以将其连接到您的表单。

<Form>
  <Field name="email">
    {(field, props) => (
      <input {...props} type="email" value={field.value} />
    )}
  </Field>
  <Field name="password">
    {(field, props) => (
      <input {...props} type="password" value={field.value} />
    )}
  </Field>
  <button type="submit">Login</button>
</Form>

这种 API 设计导致了一个完全类型安全的表单。此外,它让您可以完全控制用户界面。您可以开发自己的 TextInput 组件或连接预构建的组件库。

输入验证

模块化表单的核心功能之一是输入验证。您可以为此使用 Valibot 或 Zod 模式,也可以使用我们的内部验证函数。为了使本指南保持简单,我们使用之前创建的 Valibot 模式并将其传递给 useForm 钩子。

valiForm$ 是一个适配器,它将 Valibot 的错误消息转换为模块化表单期望的格式。对于 Zod,请使用 zodForm$ 代替。

const [loginForm, { Form, Field, FieldArray }] = useForm<LoginForm>({
  loader: useFormLoader(),
  validate: valiForm$(LoginSchema),
});

现在您只需要在发生错误时显示字段的错误消息即可。

<Field name="email">
  {(field, props) => (
    <div>
      <input {...props} type="email" value={field.value} />
      {field.error && <div>{field.error}</div>}
    </div>
  )}
</Field>

处理提交

在最后一步中,您只需要在提交表单时通过函数访问值,以进一步处理和使用它们。您可以为此使用 formAction$Form 组件的 onSubmit$ 属性。

export const useFormAction = formAction$<LoginForm>((values) => {
  // Runs on server
}, valiForm$(LoginSchema));
 
export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: valiForm$(LoginSchema),
  });
 
  const handleSubmit = $<SubmitHandler<LoginForm>>((values, event) => {
    // Runs on client
  });
 
  return (
    <Form onSubmit$={handleSubmit}>

    </Form>
  );
});

最终表单

如果我们现在将所有构建块组合在一起,我们将得到一个可工作的登录表单。您可以在下面看到组装后的代码,并在附带的沙盒中试用它。

// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$, type QRL } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import type { InitialValues, SubmitHandler } from '@modular-forms/qwik';
import { formAction$, useForm, valiForm$ } from '@modular-forms/qwik';
import * as v from 'valibot';
 
const LoginSchema = v.object({
  email: v.pipe(
    v.string(),
    v.nonEmpty('Please enter your email.'),
    v.email('The email address is badly formatted.'),
  ),
  password: v.pipe(
    v.string(),
    v.nonEmpty('Please enter your password.'),
    v.minLength(8, 'Your password must have 8 characters or more.'),
  ),
});
 
type LoginForm = v.InferInput<typeof LoginSchema>;
 
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
  email: '',
  password: '',
}));
 
export const useFormAction = formAction$<LoginForm>((values) => {
  // Runs on server
}, valiForm$(LoginSchema));
 
export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: valiForm$(LoginSchema),
  });
 
  const handleSubmit: QRL<SubmitHandler<LoginForm>> = $((values, event) => {
    // Runs on client
    console.log(values);
  });
 
  return (
    <Form onSubmit$={handleSubmit}>
      <Field name="email">
        {(field, props) => (
          <div>
            <input {...props} type="email" value={field.value} />
            {field.error && <div>{field.error}</div>}
          </div>
        )}
      </Field>
      <Field name="password">
        {(field, props) => (
          <div>
            <input {...props} type="password" value={field.value} />
            {field.error && <div>{field.error}</div>}
          </div>
        )}
      </Field>
      <button type="submit">Login</button>
    </Form>
  );
});

总结

您已经学习了模块化表单的基础知识,并准备创建您的第一个简单表单。有关更多信息和详细信息,您可以在我们的网站上找到更多指南和 API 参考:modularforms.dev

您是否喜欢模块化表单?如果您在 GitHub 上给我们一颗星,我们将不胜荣幸!

贡献者

感谢所有帮助改进本文档的贡献者!

  • fabian-hiller
  • igorbabko
  • RaeesBhatti
  • uceumice
  • Benny-Nottonson
  • mrhoodz