Quasar QForm 深度实践:表单渲染、子组件内部校验与原生提交控制
2026/9/20 18:14:07 网站建设 项目流程

Quasar QForm 深度实践:表单渲染、子组件内部校验与原生提交控制

【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar

QForm 是 Quasar 框架中负责渲染原生<form>元素并承担“校验编排器”职责的 Vue 组件:它能自动收集表单内所有支持 Quasar 校验 API 的子组件(QInput、QSelect、QField 包装组件等),统一触发其基于rules的内部校验(internal validation),并接管焦点管理、@submit/@reset事件流。读完本文,你可以掌握 QForm 的完整用法——基本校验表单、编程式调用validate()、关闭浏览器自动补全、原生提交到 URL、让自定义组件接入 QForm 校验体系,以及无障碍(a11y)行为细节,并且能看到每个行为背后在源码中的真实实现位置。

什么是 QForm:为校验而生的表单容器

The QForm component 渲染一个<form>DOM 元素,并允许你轻松校验子表单组件(如 QInput、QSelect 或你用 QField 包装的组件),前提是这些子组件使用的是内部校验(通过rules关联),而不是外部校验(external validation)。

从源码结构看,这套父子通信机制基于 Vue 的provide/inject实现:QForm 通过formKey符号向子树注入一个包含bindComponent/unbindComponent方法的对象,子组件挂载时注册自身、卸载时注销自身。相关实现见 QForm 的 provide(formKey) 逻辑,以及子组件侧的 useFormChild 组合式函数 和 QFormChildMixin。

在使用前,官方文档明确要求注意以下几点(原文档 WARNING 完整继承):

  • QForm 只钩接到 QInput、QSelect 或 QField 包装的组件上;
  • 这些组件必须使用内部校验rules),而不是外部校验;
  • validate()方法只运行组件的内部校验(即它们的rules)。原生 HTML 约束(如底层原生 input 上的type="email"required属性)是由浏览器在原生表单提交时强制的,validate()不会参考它们——因此请把这些约束也表达为 rules(例如:rules="['email']");
  • 如果你想利用reset功能,务必同时捕获 QForm 的@reset事件,并在其 handler 中重置所有被包装组件的 model。

校验的执行策略:greedy与否

QForm 提供greedy属性控制校验策略。QForm.json 的接口定义 说明其默认行为是“在发现第一个无效字段(同步校验)后即停止”。对照 validate() 源码 可以确认两条执行路径:

  • props.greedy为真时,用Promise.all(registeredComponents.map(validateComponent))并发校验所有子组件,最后过滤出全部无效项——也就是说greedy下会收集到所有错误,而不只是第一个;
  • 默认(非 greedy)时,通过reduce构建一个串行 Promise 链,一旦某个组件校验失败(if (!r.valid) throw r)就中断,只聚焦第一个无效组件。

此外,validateComponent 的防御性处理 值得一提:如果某个子组件的validate()同步抛出异常,会被视为“校验失败”而不是“表单崩溃”;如果返回了 nullish(违反契约的validate()),同样判为失败——单个坏组件不会破坏整个表单的校验流程。

基本用法

以下是文档内置的 Basic 示例(对应 docs/src/examples/QForm/Basic.vue),展示了 QForm + 内部校验rules+ Submit/Reset 按钮 +@reset手动重置 model 的完整套路:

<div class="q-pa-md" style="max-width: 400px"> <q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md"> <q-input filled v-model="name" label="Your name *" hint="Name and surname" lazy-rules :rules="[val => (val && val.length > 0) || 'Please type something']" /> <q-input filled type="number" v-model.number="age" label="Your age *" lazy-rules :rules="[ val => (val !== null && val !== '') || 'Please type your age', val => (val > 0 && val < 100) || 'Please type a real age' ]" /> <q-toggle v-model="accept" label="I accept the license and terms" /> <div> <q-btn label="Submit" type="submit" color="primary" /> <q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" /> </div> </q-form> </div>
// <script setup> import { useQuasar } from 'quasar' import { ref } from 'vue' const $q = useQuasar() const name = ref(null) const age = ref(null) const accept = ref(false) function onSubmit() { if (accept.value !== true) { $q.notify({ color: 'red-5', textColor: 'white', icon: 'warning', message: 'You need to accept the license and terms first' }) } else { $q.notify({ color: 'green-4', textColor: 'white', icon: 'cloud_done', message: 'Submitted' }) } } function onReset() { name.value = null age.value = null accept.value = false }

几个值得注意的细节:

  • 两个q-input都使用了lazy-rules——只在校验被触发后(而非输入过程中)才应用规则,避免用户还没填完就被标红;
  • 注意q-toggle上的accept并没有走 QForm 的rules体系,而是靠@submithandler 里的手动判断(未接受时用$q.notify报警告);
  • onReset中手动把三个 model 归零,正是前文 WARNING 中“捕获@reset并重置所有被包装组件 model”这一要求的落地写法。

用 QBtn 激活 @submit 与 @reset

为了让用户能够激活表单上的@submit@reset事件,创建一个type设为submitreset的 QBtn:

<div> <q-btn label="Submit" type="submit" color="primary" /> <q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" /> </div>

从 submit() 的源码实现 看,其内部流程是:先stopAndPrevent(evt)阻止默认提交行为,然后调用validate();当且仅当本次提交未“过期”(index === validateIndex,即期间没有触发新的校验)且校验通过时,若组件上绑定了onSubmitprop(即@submit监听器存在)则emit('submit', evt),否则调用evt.target.submit()走原生提交。这解释了为什么@submit只在所有校验通过后才会被调用。

编程式校验:validate() 与 resetValidation()

除了按钮触发,还可以给 QForm 一个 Vue ref,直接调用validateresetValidation函数。

Composition API 写法:

// <q-form ref="myFormRef"> setup () { const myFormRef = useTemplateRef('myFormRef') function validate () { myFormRef.value.validate().then(success => { if (success) { // yay, models are correct } else { // oh no, user has filled in // at least one invalid value } }) } // to reset validations: function reset () { myFormRef.value.resetValidation() } return { // ... } }

Options API 写法:

// <q-form ref="myForm"> this.$refs.myForm.validate().then(success => { if (success) { // yay, models are correct } else { // oh no, user has filled in // at least one value is invalid } }) // to reset validations: this.$refs.myForm.resetValidation()

根据 接口定义:

  • validate(shouldFocus)触发对表单内所有适用 Quasar 组件的校验,返回一个总是被 fulfillPromise<boolean>true表示校验成功,false表示检测到无效 model)。可选参数shouldFocus(Boolean)指定是否聚焦到出错组件,指定时会覆盖no-error-focusprop;
  • resetValidation()重置表单内所有适用组件的校验状态。从 resetValidation 源码 可见,它先自增validateIndex(使所有进行中的旧校验结果“过期”,避免旧结果覆盖新结果),然后遍历已注册组件逐一调用其resetValidation()(若存在)。

校验触发时还会发出两个事件(见 events 定义):

  • validation-success:校验触发后,所有内部 Quasar 组件的 model 均有效;
  • validation-error:校验触发后,至少一个内部组件的 model 无效,事件携带ref参数——第一个触发校验错误的组件实例引用。

校验失败后的焦点处理同样有源码依据:在 validate() 的错误分支 中,若未设置no-error-focus,QForm 会从错误列表中找到第一个已挂载且未销毁、实现了focus方法的组件并调用focus(),把键盘焦点移到第一个无效字段上。

关闭浏览器自动补全

如果你希望关闭部分浏览器对表单内所有 input 元素使用的自动纠错或拼写检查行为,可以给 QForm 组件添加这些纯 HTML 属性:

autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false"

由于 QForm 直接渲染为<form>元素,这些属性会原样落到 DOM 上,由浏览器自身解释。

原生提交到 URL(Native Form Submit)

如果你在 QForm 上使用原生的actionmethod属性,请记住必须给每个 Quasar 表单组件使用nameprop,这样实际发送的 formData 才会包含用户填写的内容:

<q-form action="https://some-url.com" method="post"> <q-input name="firstname" ...> <!-- ... --> </q-form>

行为规则如下(这些规则与 submit() 源码 的分支逻辑一一对应):

  • 通过设置 QForm 的actionmethodenctypetarget属性来控制表单的提交方式;
  • 如果 QForm 上没有@submit监听器,则校验成功后表单会自动进行原生提交(源码中即evt.target.submit()分支);
  • 如果 QForm 上存在@submit监听器,则校验成功后会调用该监听器。此时若要继续执行原生提交,需要手动触发:
<q-form action="https://some-url.com" method="post" @submit.prevent="onSubmit"> <q-input name="firstname" ...> <!-- ... --> </q-form>
methods: { onSubmit (evt) { console.log('@submit - do something here', evt) evt.target.submit() } }

自定义子组件接入 QForm:Child communication

默认情况下,所有 Quasar 表单组件都会与父 QForm 实例通信。如果(出于某种原因)你在创建自己的表单组件(且不包装 Quasar 表单组件),可以通过以下方式让 QForm 感知到它。

Composition API:

import { useFormChild } from 'quasar' setup () { // function validate () { ... } useFormChild({ validate, // Function; Can be async; // Should return a Boolean (or a Promise resolving to a Boolean) resetValidation, // Optional function which resets validation requiresQForm: true // should it error out if no parent QForm is found? }) }

Options API:

import { QFormChildMixin } from 'quasar' // some component export default { mixins: [ QFormChildMixin ], methods: { // required! should return a Boolean // or a Promise resolving to a Boolean validate () { console.log('called my-comp.validate()') return true }, // optional function resetValidation () { // ... } }, // ... }

从 useFormChild 源码 可以看到其内部契约:

  • 通过inject(formKey, false)获取父级 QForm;若拿不到且requiresQForm为真,则console.error('Parent QForm not found on useFormChild()!')
  • 通过Object.assign(proxy, { validate, resetValidation })把你的validate/resetValidation暴露为组件实例上的公开方法——这正是 QForm 遍历时能够调用的入口(QFormChildMixin 中也定义了同名的空占位方法);
  • onMounted时(若disable不为真)调用$form.bindComponent(proxy)注册自己,onBeforeUnmount时注销;
  • 还 watch 了props.disable:一旦组件被禁用,就调用resetValidation()并从 QForm 解绑,恢复启用时重新绑定——被禁用的字段不会参与表单校验。

Options API 的 QFormChildMixin 以相同的方式工作:mounted钩子里向this.$.provides[formKey]注册,beforeUnmount注销,并 watchdisable做相同的解绑/绑定处理。

另外,QForm 实例还提供了getValidationComponents()方法(见 方法定义 与 暴露逻辑),返回一个支持 Quasar 校验 API 的子组件实例数组(来自 QField 派生,或使用了useFormChild()/QFormChildMixin的组件),便于你自行遍历处理校验组件。

无障碍(Accessibility,v2.25+)

QForm 渲染的是原生<form>元素,因此浏览器的内置表单语义(包括按Enter隐式提交)原样生效。围绕无障碍的关键行为:

  • 校验失败时聚焦:QForm 会把键盘焦点移动到第一个无效字段,可用no-error-focusprop 关闭(对应 validate() 中的 focus 分支);
  • 错误播报:屏幕阅读器通过该字段自身的role="alert"消息获取其错误——参见 QField 文档的 Accessibility 一节;
  • autofocus prop:表单挂载时聚焦第一个[autofocus]元素(找不到则回退到第一个可聚焦元素)。从 focus() 的源码 可见其选择器策略是逐级降级:先找[autofocus][tabindex]/[data-autofocus][tabindex],再找[autofocus] [tabindex],再找不带 tabindex 的[autofocus]/[data-autofocus],最后find第一个tabIndex !== -1[tabindex]元素;
  • 没有聚合错误摘要:屏幕阅读器用户听到的是“获得焦点的那个字段”的 alert,而不是“总共多少字段失败”。对于长表单,官方建议在校验失败时自行渲染一个 live region(例如 “3 fields need attention”)。

另外两个相关 prop:no-reset-focus(reset 表单时不聚焦第一个组件)与greedy(见前文)。reset 流程从 reset() 源码 看:先emit('reset')(给业务代码重置 model 的机会),再在nextTick中调用resetValidation(),并在autofocus且未设置no-reset-focus时重新聚焦——顺序保证了用户态先于校验态重置。

关键文件索引

内容路径
QForm 组件实现(validate/submit/reset/focus/子组件注册)ui/src/components/form/QForm.js
QForm props / events / methods 接口定义ui/src/components/form/QForm.json
Options API 混入实现ui/src/components/form/QFormChildMixin.js
Composition API 组合式函数实现ui/src/composables/use-form/use-form-child.js
官方 Basic 示例docs/src/examples/QForm/Basic.vue
相关文档:QField / QInput / QSelectfield.md、input.md、select.md

【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar

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

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

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

立即咨询