模块化表单

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

要开始使用,请安装 npm 包:

npm install @modular-forms/qwik

定义表单

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

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

由于 Modular Forms 支持 ValibotZod 进行输入验证,您可以选择从模式派生类型定义。

import { email, type Input, minLength, object, string } from 'valibot';
 
const LoginSchema = object({
  email: string([
    minLength(1, '请输入您的电子邮件。'),
    email('电子邮件地址格式不正确。'),
  ]),
  password: string([
    minLength(1, '请输入您的密码。'),
    minLength(8, '您的密码必须包含 8 个或更多字符。'),
  ]),
});
 
type LoginForm = Input<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">登录</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) => {
  // 在服务器上运行
}, valiForm$(LoginSchema));
 
export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: valiForm$(LoginSchema),
  });
 
  const handleSubmit = $<SubmitHandler<LoginForm>>((values, event) => {
    // 在客户端上运行
  });
 
  return (
    <Form onSubmit$={handleSubmit}>

    </Form>
  );
});

最终表单

现在,我们将所有构建块组合在一起,就可以得到一个可工作的登录表单。下面是组装的代码,并可以在附带的沙箱中尝试它。

// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$ } 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 { email, type Input, minLength, object, string } from 'valibot';
 
const LoginSchema = object({
  email: string([
    minLength(1, 'Please enter your email.'),
    email('The email address is badly formatted.'),
  ]),
  password: string([
    minLength(1, 'Please enter your password.'),
    minLength(8, 'Your password must have 8 characters or more.'),
  ]),
});
 
type LoginForm = Input<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: 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 上得到您的星星!

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • fabian-hiller