# 表单

使用验证规则收集用户信息。

## Features

- 基于原生浏览器<!-- --> [约束验证 API。](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation)

- 支持内置验证。

- 支持自定义验证。

- 完全自定义验证消息。

- 可访问的验证消息。

- 支持客户端和服务器端场景。

- 焦点完全管理。

## [构造](#构造)

导入所有部分并将它们组合在一起。

```jsx
import { Form } from "radix-ui";

export default () => (
  <Form.Root>
    <Form.Field>
      <Form.Label />

      <Form.Control />

      <Form.Message />

      <Form.ValidityState />
    </Form.Field>

    <Form.Message />

    <Form.ValidityState />

    <Form.Submit />
  </Form.Root>
);
```

## [API 参考](#api-参考)

### [Root](#root)

包含表单的所有部分。

| Prop                  | Type       | Default          |
| --------------------- | ---------- | ---------------- |
| `asChild`             | `boolean`  | `false`          |
| `onClearServerErrors` | `function` | No default value |

### [Field](#field)

字段的包装器。它自动处理 ID/名称和标签的可访问性。

| Prop            | Type      | Default          |
| --------------- | --------- | ---------------- |
| `asChild`       | `boolean` | `false`          |
| `name*`         | `string`  | No default value |
| `serverInvalid` | `boolean` | No default value |

| Data attribute   | Values         |
| ---------------- | -------------- |
| `[data-invalid]` | 字段无效时存在 |
| `[data-valid]`   | 字段有效时存在 |

### [Label](#label)

一个标签元素，当嵌套在 `Field` 部分内时会自动连接。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

| Data attribute   | Values         |
| ---------------- | -------------- |
| `[data-invalid]` | 字段无效时存在 |
| `[data-valid]`   | 字段有效时存在 |

### [Control](#control)

一个控制元素（默认是 `input`），当嵌套在 `Field` 部分内时会自动连接。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

| Data attribute   | Values         |
| ---------------- | -------------- |
| `[data-invalid]` | 字段无效时存在 |
| `[data-valid]`   | 字段有效时存在 |

### [Message](#message)

当嵌套在 `Field` 部分内时，验证消息会自动连接（功能和可访问性）到给定的控件。它可用于内置和自定义客户端验证，以及服务器端验证。当在 `Field` 外部使用时，必须传递一个与字段相匹配的 `name` 属性。

`Form.Message` 接受一个 `match` 属性，用于确定何时应显示该消息。它匹配原生 HTML 有效性状态（`ValidityState` 在 [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) 上），该状态会验证 `required`、`min`、`max` 等属性。如果控件的有效性状态中的给定 `match` 为 `true`，则消息将显示。

您还可以将函数传递给 `match` 以提供自定义验证规则。

| Prop         | Type      | Default          |
| ------------ | --------- | ---------------- |
| `asChild`    | `boolean` | `false`          |
| `match`      | `Matcher` | No default value |
| `forceMatch` | `boolean` | `false`          |
| `name`       | `string`  | No default value |

### [ValidityState](#validitystate)

使用此渲染属性组件访问给定字段的有效性状态（请参见 [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) 上的 `ValidityState`）。在嵌套在 `Field` 部分内时，字段的有效性会自动可用，否则必须传递一个 `name` 属性以关联它。

| Prop       | Type       | Default          |
| ---------- | ---------- | ---------------- |
| `children` | `function` | No default value |
| `name`     | `string`   | No default value |

### [Submit](#submit)

提交按钮。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

## [示例](#示例)

### [与您自己的组件组合](#与您自己的组件组合)

使用 `asChild`，您可以将 `Form` 原语部分与您自己的组件组合。

```jsx
<Form.Field name="name">
  <Form.Label>全名</Form.Label>

  <Form.Control asChild>
    <TextField.Input variant="primary" />
  </Form.Control>
</Form.Field>
```

它也可以用于组合其他类型的控件，例如 `select`：

```jsx
<Form.Field name="country">
  <Form.Label>国家</Form.Label>

  <Form.Control asChild>
    <select>
      <option value="uk">英国</option>…
    </select>
  </Form.Control>
</Form.Field>
```

> 注意: 目前还不可能将 `Form` 与 Radix 的其他表单原语如 `Checkbox`、`Select` 等组合。我们正在寻找解决方案。

### [提供您自己的验证消息](#提供您自己的验证消息)

当未提供 `children` 时，`Form.Message` 将为给定的 `match` 渲染默认错误消息。

```jsx
// 将产生 "此值缺失"

<Form.Message match="valueMissing" />
```

通过传递您自己的 `children`，可以提供更有意义的消息。这对于国际化也很有用。

```jsx
// 将产生 "请提供一个名称"

<Form.Message match="valueMissing">请提供一个名称</Form.Message>
```

### [自定义验证](#自定义验证)

除了上述所有内置的客户端验证匹配之外，您还可以在仍然利用平台验证能力的同时提供自己的自定义验证。它使用 [约束验证 API](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) 中的 `customError` 类型。

可以将您自己的验证函数传递到 `Form.Message` 上的 `match` 属性。以下是一个示例：

```jsx
<Form.Field name="name">
  <Form.Label>全名</Form.Label>

  <Form.Control />

  <Form.Message match={(value, formData) => value !== "John"}>只有约翰被允许。</Form.Message>
</Form.Field>
```

> `match` 将以控件的当前值作为第一个参数，整个 `FormData` 作为第二个参数调用。 `match` 也可以是一个 `async` 函数（或返回一个 promise）以执行异步验证。

### [基于有效性进行样式设置](#基于有效性进行样式设置)

我们向相关部分添加了 `data-valid` 和 `data-invalid` 属性。利用它相应地设置您的组件样式。以下是样式设置 `Label` 部分的示例。

```jsx
//index.jsx

import * as React from "react";

import { Form } from "radix-ui";

export default () => (
  <Form.Root>
    <Form.Field name="email">
      <Form.Label className="FormLabel">电子邮件</Form.Label>

      <Form.Control type="email" />
    </Form.Field>
  </Form.Root>
);
```

```css
/* styles.css */

.FormLabel[data-invalid] {
  color: red;
}

.FormLabel[data-valid] {
  color: green;
}
```

### [访问有效性状态以获得更多控制](#访问有效性状态以获得更多控制)

您可能需要访问字段的原始有效性状态，以显示自己定义的图标，或通过其定义的属性与组件库进行交互。您可以通过使用 `Form.ValidityState` 部分来做到这一点：

```jsx
<Form.Field name="name">
  <Form.Label>全名</Form.Label>

  <Form.ValidityState>
    {(validity) => (
      <Form.Control asChild>
        <TextField.Input variant="primary" state={getTextFieldInputState(validity)} />
      </Form.Control>
    )}
  </Form.ValidityState>
</Form.Field>
```

### [服务器端验证](#服务器端验证)

该组件还支持使用相同的 `Form.Message` 组件进行服务器端验证。 您可以通过传递 `forceMatch` 属性来重用为客户端错误定义的相同消息，无论客户端匹配逻辑如何，均强制显示该消息。

如果客户端上不存在该消息，您也可以渲染没有 `match` 的 `Form.Message`。 通过将 `serverInvalid` 布尔属性传递给 `Form.Field` 部分，可以将字段标记为无效。

以下是带有服务器端错误处理的示例：

```jsx
import * as React from "react";

import { Form } from "radix-ui";

function Page() {
  const [serverErrors, setServerErrors] = React.useState({
    email: false,

    password: false,
  });

  return (
    <Form.Root
      // `onSubmit` 仅在通过客户端验证时触发
      onSubmit={(event) => {
        const data = Object.fromEntries(new FormData(event.currentTarget));

        // 提交表单数据并捕获响应中的错误
        submitForm(data)
          .then(() => {})
          /**
           * 将服务器响应中的错误映射到您喜欢的结构。
           * 在这种情况下 resulting in this object: `{ email: false, password: true }`
           */
          .catch((errors) => setServerErrors(mapServerErrors(errors)));

        // 防止默认表单提交
        event.preventDefault();
      }}
      onClearServerErrors={() => setServerErrors({ email: false, password: false })}
    >
      <Form.Field name="email" serverInvalid={serverErrors.email}>
        <Form.Label>电子邮件地址</Form.Label>

        <Form.Control type="email" required />

        <Form.Message match="valueMissing">请输入您的电子邮件。</Form.Message>

        <Form.Message match="typeMismatch" forceMatch={serverErrors.email}>
          请输入有效的电子邮件。
        </Form.Message>
      </Form.Field>

      <Form.Field name="password" serverInvalid={serverErrors.password}>
        <Form.Label>密码</Form.Label>

        <Form.Control type="password" required />

        <Form.Message match="valueMissing">请输入密码。</Form.Message>

        {serverErrors.password && (
          <Form.Message>请提供有效的密码。它应包含至少 1 个数字和 1 个特殊字符。</Form.Message>
        )}
      </Form.Field>

      <Form.Submit>提交</Form.Submit>
    </Form.Root>
  );
}
```

您应该使用 `Form.Root` 部分的 `onClearServerErrors` 回调属性清除服务器错误。在表单重新提交之前和表单重置时，它将清除服务器错误。

此外，这还提供了何时重置单个服务器错误的控制。例如，您可以在用户编辑它时立即重置电子邮件服务器错误：

```jsx
<Form.Field name="email" serverInvalid={serverErrors.email}>
  <Form.Label>电子邮件地址</Form.Label>

  <Form.Control
    type="email"
    onChange={() => setServerErrors((prev) => ({ ...prev, email: false }))}
  />

  <Form.Message match="valueMissing">请输入您的电子邮件。</Form.Message>

  <Form.Message match="typeMismatch" forceMatch={serverErrors.email}>
    请输入有效的电子邮件。
  </Form.Message>
</Form.Field>
```

## [可访问性](#可访问性)

该组件遵循“内联错误”模式进行验证：

- 使用 `Form.Field` 中提供的 `name` 将标签和控件关联
- 当一个或多个客户端错误消息显示时，它们会自动与其匹配控件关联，并相应地宣布
- 焦点移到第一个无效控件

<!--$-->

<!--/$-->
