☰
Ariakit Form 组件实战指南:无障碍表单的提交、校验与状态管理
2026/9/25 5:20:27 网站建设 项目流程
  • UI组件
  • 前端

【免费下载链接】ariakit

Toolkit with accessible components, styles, and examples for your next web app

项目地址:https://gitcode.com/gh_mirrors/ar/ariakit
点击查看免费下载

Ariakit 的 Form 组件基于 WAI-ARIA Form Role 设计,为 React 应用提供了一整套自带无障碍支持的表单交互方案:从字段状态管理、浏览器内置校验的接入,到自定义校验与异步提交,再到错误提示的展示与聚焦。读完本文,你将掌握useFormStore、useFormSubmit、useFormValidate等核心 API 的完整用法,并能结合源码理解 Ariakit 表单内部的状态流转与无障碍细节,直接落地到生产项目。

组件与 API 总览

Ariakit 的表单体系由一个 store 与一组配套组件组成。store 通过useFormStore创建,负责统一管理values(字段值)、errors(错误信息)与touched(字段是否被触碰过)三份核心状态;组件则通过storeprop 或上下文拿到 store,完成渲染与交互。

完整 API 一览:

useFormStore() useFormContext() useFormValue() useFormValidate() useFormSubmit() <FormProvider> <Form> <FormGroup> <FormGroupLabel /> <FormLabel /> <FormControl /> <FormInput /> <FormCheckbox /> <FormDescription /> <FormError /> <FormPush /> <FormRemove /> </FormGroup> <FormRadioGroup> <FormRadio /> </FormRadioGroup> <FormReset /> <FormSubmit /> </Form> </FormProvider>
  • Hook:useFormStore创建表单 store;useFormContext读取上下文中的 store;useFormValue订阅单个字段值并在其变化时触发重渲染;useFormValidate/useFormSubmit分别向 store 注册校验与提交回调。
  • 结构组件:FormProvider(提供上下文,适合 store 不在Form内创建的场景)、Form、FormGroup、FormGroupLabel。
  • 字段组件:FormLabel、FormControl、FormInput、FormCheckbox、FormRadio、FormRadioGroup、FormDescription、FormError。
  • 数组字段组件:FormPush(向数组字段追加值)、FormRemove(移除指定下标的值)。
  • 操作组件:FormReset、FormSubmit。

一个最小可运行示例(对应仓库中的 examples/form/index.react.tsx):

import * as Ariakit from "@ariakit/react"; function Example() { const form = Ariakit.useFormStore({ defaultValues: { name: "", email: "" }, }); Ariakit.useFormSubmit(form, async (state) => { alert(JSON.stringify(state.values)); }); return ( <Ariakit.Form store={form} aria-labelledby="add-new-participant"> <h2 id="add-new-participant">Add new participant</h2> <div className="field"> <Ariakit.FormLabel name={form.names.name}>Name</Ariakit.FormLabel> <Ariakit.FormInput name={form.names.name} placeholder="John Doe" required /> <Ariakit.FormError name={form.names.name} className="error" /> </div> <div className="field"> <Ariakit.FormLabel name={form.names.email}>Email</Ariakit.FormLabel> <Ariakit.FormInput type="text" name={form.names.email} placeholder="johndoe@example.com" required /> <Ariakit.FormError name={form.names.email} className="error" /> </div> <div className="buttons"> <Ariakit.FormReset className="button secondary reset">Reset</Ariakit.FormReset> <Ariakit.FormSubmit className="button">Add</Ariakit.FormSubmit> </div> </Ariakit.Form> ); }

理解表单 store:状态与字段名代理

useFormStore是整套表单的枢纽。它在 React 侧封装了底层核心 store(核心实现在 packages/ariakit-components/src/form/form-store.ts),由以下三份数据驱动:

状态说明默认值
values表单字段的当前值,任意嵌套对象{}
errors与values结构对应的错误信息(DeepPartial){}
touched字段是否被触碰过(DeepPartial<DeepMap<T, boolean>>){}

此外还维护了派生状态:valid(当前是否有效)、validating(是否正在校验)、submitting(是否正在提交),以及submitSucceed/submitFailed两个计数器,分别记录提交成功与失败的次数。

store 提供的方法分为几组:

  • 值操作:getValue(name)/setValue(name, value)支持点分路径读取与写入嵌套值;pushValue(name, value)/removeValue(name, index)用于数组字段——注意removeValue会用null占位被删除的下标,以保持索引稳定(详见 form-store.ts),提交前如需省略被删除项,应自行过滤null。
  • 错误操作:getError(name)/setError(name, error)/setErrors(errors),其中setErrors可接收函数式更新。
  • 触碰状态:getFieldTouched(name)/setFieldTouched(name, value)/setTouched(touched)。
  • 流程控制:onValidate(callback)/validate()、onSubmit(callback)/submit()、reset()。

一个非常实用的细节是form.names:它是一个基于Proxy的字段名代理(form-store.ts)。访问form.names.name.first会返回字符串"name.first",既避免了手写魔法字符串拼错路径,又能获得完整的 TypeScript 类型提示;同时它支持Symbol.toPrimitive/toString,在 React 中作为 children 渲染也不会报错。

提交表单:注册提交处理器

useFormSubmit用于在表单 store 上注册提交处理器。当用户提交表单,或代码调用form.submit()时,所有已注册的处理器会按注册顺序依次执行。

提交处理器可以返回 Promise,并且能直接与 store 交互——这意味着我们可以在提交时读取表单values,并在失败时调用setErrors把服务端返回的错误展示到界面上:

const form = useFormStore(); useFormSubmit(form, async (state) => { const response = await fetch("https://jsonplaceholder.typicode.com/posts", { method: "POST", body: JSON.stringify(state.values), headers: { "Content-type": "application/json; charset=UTF-8", }, }); if (!response.ok) { form.setErrors(await response.json()); } });

从源码看,submit()的完整流程是(form-store.ts):

  1. 将submitting置为true,并把所有字段的touched一次性置为true(调用setAll(values, true),意味着提交后错误会立刻可见);
  2. 先执行校验:调用validate(),若校验失败则submitFailed + 1并返回false;
  3. 校验通过后,按注册顺序依次执行所有 submit 回调(串行执行以保证顺序可预测,见源码中引用的 issue #2282);
  4. 等待下一帧(nextFrame,即requestAnimationFrame与 100ms 超时的竞速,确保隐藏标签页也能推进流程),再次检查errors:为空则submitSucceed + 1并返回true,否则视为失败;
  5. 无论成败,最终都会在finally中将submitting复位。

useFormSubmit内部通过useEvent保持回调引用稳定,并监听 store 的items变化来重置回调顺序(form-store.ts),这样即使字段是懒加载渲染的,回调执行顺序依然一致。

表单校验:内置校验与自定义校验

浏览器内置校验

Ariakit 完整支持浏览器内置的表单校验。直接在字段上使用required、minLength、maxLength、min、max、type、pattern等原生属性,即可获得开箱即用的简单校验。

这种方式最大的优势是:错误消息由浏览器自动本地化为用户当前语言。但它也有明显局限:默认的错误提示 UI 不一定符合无障碍标准,样式不可定制,且无法精确控制错误提示的显示时机。

好在我们可以通过 JavaScript 的Constraint Validation API介入这一过程——这正是FormControl内部做的事情。它让错误消息可以以无障碍、可定制的方式展示出来。

在源码中,FormControl会通过useFormValidate注册一个校验回调(form-control.tsx):它会找到与字段名匹配的真实 DOM 元素(通过element.form.elements.namedItem(name)在表单内查找),等待一个微任务让validity状态就绪,然后若element.validity.valid为false,就把element.validationMessage(即浏览器本地化的错误文案)写入 store:

useFormValidate(form, async () => { const element = getNamedElement(ref, name); if (!element) return; await Promise.resolve(); if ("validity" in element && !element.validity.valid) { form.setError(name, element.validationMessage); } });

同时,Form组件会渲染noValidate属性(form.tsx),关闭浏览器默认的原生错误气泡,把校验与展示完全交给 store 与组件体系。

自定义校验

与useFormSubmit类似,useFormValidate用来在表单 store 上注册校验处理器。校验在字段被触碰或表单提交时触发。

校验回调可以像普通 hook 一样被拆到独立组件中,作为 prop 传入,实现字段级校验:

function MyForm() { const form = useFormStore({ defaultValues: { name: "" } }); return ( <Form store={form}> <NameInput store={form} name={form.names.name} /> </Form> ); } function NameInput({ store, name, ...props }) { useFormValidate(store, () => { const value = store.getValue(name); if (value.length < 3) { store.setError(name, "Name must be at least 3 characters long"); } }); return <FormInput name={name} {...props} />; }

validate()的内部实现(form-store.ts)同样采用串行执行:先把validating置为true、清空errors,再按注册顺序逐个 await 校验回调,最后等待下一帧并依据errors是否为空返回布尔结果。

校验与重置的触发时机

Form组件提供了几个布尔选项来控制行为(源码见 form.tsx):

选项默认值作用
validateOnChangetrue字段值变化时触发校验回调(通过useUpdateEffect监听values,跳过与初始值相等的时刻)
validateOnBlurtrue字段失焦时触发校验回调(仅当失焦目标确认为本表单字段时)
autoFocusOnSubmittrue提交后自动聚焦第一个无效字段(若为文本类字段还会自动全选文本)
resetOnSubmittrue提交成功后把表单重置为defaultValues
resetOnUnmountfalse组件卸载时重置表单状态

其中autoFocusOnSubmit的实现会遍历按 DOM 位置排序的表单项,找到第一个aria-invalid="true"的字段并调用element.focus()(form.tsx)——这是无障碍表单的关键一环:提交失败时,屏幕阅读器用户和键盘用户能直接到达出错位置。

错误消息的展示:FormError

FormError组件用于渲染单个字段的错误消息,默认渲染为div,并自动带上role="alert"(form-error.tsx),确保错误出现时能被屏幕阅读器即时播报。

它的渲染逻辑有一个值得注意的细节:children只有在字段存在错误且字段已被触碰(getFieldTouched(name)为true)时才会显示(form-error.tsx)。这意味着在用户离开字段之前,校验错误不会打扰输入过程;一旦用户触碰过该字段(失焦或提交),错误就立刻可见。这也与submit()中“提交时将所有字段标记为 touched”的行为互相呼应。

样式:基于 aria-invalid 定制错误态

FormControl是FormInput、FormCheckbox、FormRadio等字段组件的共同基础。当字段无效时,它会自动把aria-invalid属性设为"true"(form-control.tsx),判定条件是“存在错误且字段已被触碰”:

invalid: () => !!form.getError(name) && form.getFieldTouched(name),

因此,可以直接用属性选择器定制无效状态下的视觉样式:

.field[aria-invalid="true"] { /* 例如红色边框 */ border-color: red; }

aria-invalid本身也是无障碍标准的一部分:屏幕阅读器会在用户进入该字段时播报“无效”状态。此外,FormControl还会自动把aria-labelledby指向对应的FormLabel、把aria-describedby拼接FormError与FormDescription的 id(form-control.tsx),确保标签、错误与描述在无障碍 API 层面正确关联。更多样式技巧可参考仓库中的 Styling 指南。

更多无障碍细节

  • FormLabel:如果字段是原生input、textarea、select等元素,FormLabel渲染为原生<label>并依赖htmlFor;否则渲染为<span>,依靠字段上的aria-labelledby建立关联,点击标签仍会把焦点移到字段上(form-label.tsx),对自定义控件(如富文本编辑器)尤其友好。
  • FormSubmit:渲染原生type="submit"按钮,并在submitting期间置为disabled;由于默认开启了accessibleWhenDisabled,禁用状态下按钮对键盘和屏幕阅读器依然可访问(form-submit.tsx)。
  • FormControl 与 FormInput 的区别:FormInput会自动把value与onChange传给底层元素;FormControl则不做这件事,适合把表单状态桥接到值不由原生value/onChange控制的自定义组件(如编辑器、选择器),此时通常配合useFormValue读取值、调用store.setValue写回值。

总结

Ariakit Form 组件把浏览器内置校验、store 驱动的状态管理、可定制且可访问的错误展示整合为一条完整链路:useFormStore管理状态,useFormSubmit/useFormValidate以可预测的顺序执行业务回调,Form组件接管校验时机、失败聚焦与成功重置,FormControl系列字段组件负责把无障碍属性(aria-invalid、aria-labelledby、aria-describedby、role="alert")自动接好。无论是简单联系人表单,还是带数组字段、服务端校验的复杂业务表单,这套体系都能在保持无障碍的同时让代码足够简洁。

  • UI组件
  • 前端

【免费下载链接】ariakit

Toolkit with accessible components, styles, and examples for your next web app

项目地址:https://gitcode.com/gh_mirrors/ar/ariakit
点击查看免费下载
上一篇:一文读懂T5-Base:220M参数模型的核心架构与终极优势指南
下一篇:探索IEEE-1394标准:开源文档仓库助力技术开发

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询