ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

cube-ui Form 组件完全指南:数据驱动表单与校验实战

cube-ui Form 组件完全指南:数据驱动表单与校验实战 前端UI组件移动开发【免费下载链接】cube-ui:large_orange_diamond: A fantastic mobile ui lib implement by Vue项目地址https://gitcode.com/gh_mirrors/cu/cube-ui点击查看免费下载导读cube-form是 cube-ui 从 1.7.0 起提供的表单组件它以数据驱动为核心思路开发者只需要声明model数据源与schema表单模式组件就能自动渲染出包含各类输入组件、分组结构与校验逻辑的完整表单。自 1.8.0 起它还支持 blur 离焦触发校验、校验 debounce 防抖以及与 Validator 一致的异步校验能力。读完本文你将掌握 cube-form 的完整配置方式、schema 字段定义、校验规则与事件机制并能够用组件化 插槽的方式构建自定义表单和问卷类场景表单。一、数据驱动model、schema 与 options 三要素cube-form 的设计哲学可以用一句话概括表单是数据的投影。组件实例中三个核心 Props 决定了整个表单的形态与行为对应源码 form.vue| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | model | 数据源 | Object | - |{}| | schema | 生成表单依赖的模式 | Object | - |{}| | immediateValidate | 初始化时是否立即校验 | Boolean | true/false | false | | action | 表单 Form action 的值 | String | - | undefined | | options | 配置项 | Object | - |{ scrollToInvalidField: false, layout: standard }| | submitAlwaysValidate | 提交表单时是否总校验所有字段1.12.36 | Boolean | true/false | false |model表单绑定的数据源对象表单中每个字段的值都会同步写回model的对应 key。从源码看form-item.vue通过this.form.model[this.fieldValue.modelKey] newModel完成数据回写同时也会反向监听modelVal的变化驱动组件更新。schema定义字段如何渲染、如何分组、如何校验的模式对象。immediateValidate若为true组件在mounted后立即执行一次校验见 form.vue。options全局配置目前包含scrollToInvalidField提交校验失败时是否自动滚动到第一个无效字段与layout三种布局。默认options在源码中直接体现// src/components/form/form.vue options: { type: Object, default() { return { scrollToInvalidField: false, layout: LAYOUTS.STANDARD } } }其中LAYOUTS定义在 layouts.js包含standard、classic、fresh三种取值分别对应三种视觉布局standard 为标签与控件同行、classic 为标签在上的经典样式、fresh 为小字号浮动标签样式。三种布局的具体样式差异可以查看 form.vue 中的样式定义。二、基础示例一个完整的默认表单官方文档给出了一个覆盖全部内置组件的完整示例。它同时演示了字段分组legend基础 / 高级 / 提交按钮组与各类校验配置cube-form :modelmodel :schemaschema :immediate-validatefalse :optionsoptions validatevalidateHandler submitsubmitHandler resetresetHandler/cube-formexport default { data() { return { validity: {}, valid: undefined, model: { checkboxValue: false, checkboxGroupValue: [], inputValue: , radioValue: , rateValue: 0, selectValue: 2018, switchValue: true, textareaValue: , uploadValue: [] }, schema: { groups: [ { legend: 基础, fields: [ { type: checkbox, modelKey: checkboxValue, props: { option: { label: Checkbox, value: true } }, rules: { required: true }, messages: { required: Please check this field } }, { type: checkbox-group, modelKey: checkboxGroupValue, label: CheckboxGroup, props: { options: [1, 2, 3] }, rules: { required: true } }, { type: input, modelKey: inputValue, label: Input, props: { placeholder: 请输入 }, rules: { required: true }, // validating when blur trigger: blur }, { type: radio-group, modelKey: radioValue, label: Radio, props: { options: [1, 2, 3] }, rules: { required: true } }, { type: select, modelKey: selectValue, label: Select, props: { options: [2015, 2016, 2017, 2018, 2019, 2020] }, rules: { required: true } }, { type: switch, modelKey: switchValue, label: Switch, rules: { required: true } }, { type: textarea, modelKey: textareaValue, label: Textarea, rules: { required: true }, // debounce validate // if set to true, the default debounce time will be 200(ms) debounce: 100 } ] }, { legend: 高级, fields: [ { type: rate, modelKey: rateValue, label: Rate, rules: { required: true } }, { type: upload, modelKey: uploadValue, label: Upload, events: { file-removed: (...args) { console.log(file removed, args) } }, rules: { required: true, uploaded: (val, config) { return Promise.all(val.map((file, i) { return new Promise((resolve, reject) { if (file.uploadedUrl) { return resolve() } // fake request setTimeout(() { if (i % 2) { reject(new Error()) } else { file.uploadedUrl uploaded/url resolve() } }, 1000) }) })).then(() { return true }) } }, messages: { uploaded: 上传失败 } } ] }, { fields: [ { type: submit, label: Submit }, { type: reset, label: Reset } ] } ] }, options: { scrollToInvalidField: true, layout: standard // classic fresh } } }, methods: { submitHandler(e) { e.preventDefault() console.log(submit, e) }, validateHandler(result) { this.validity result.validity this.valid result.valid console.log(validity, result.validity, result.valid, result.dirty, result.firstInvalidFieldIndex) }, resetHandler(e) { console.log(reset, e) } } }这个示例演示了几个值得注意的点trigger: blurinput 字段在离焦时才触发校验而不是每次输入都校验debounce: 100textarea 字段校验防抖 100ms若设置为true则使用默认的 200ms异步校验规则upload 字段的uploaded规则返回 Promise模拟异步上传校验这印证了文档开头同 Validator 一样也开始支持异步校验的描述事件监听validate在每次校验数据更新时触发submit在校验成功后触发reset在重置时触发。从实现上看cube-form最终渲染的是一个真正的form元素见 form.vue事件名submit/reset与原生表单事件一一对应同时组件在内部通过dispatchEvent派发事件完成提交与重置流程。schema 中的字段如何被渲染schema.groups在组件内通过groups计算属性处理若同时存在schema.fields则会自动包成一个无 legend 的分组插入到最前面见 form.vue。每个字段最终由cube-form-item渲染其componentName计算属性决定了渲染哪个组件// src/components/form/form-item.vue componentName() { const fieldValue this.fieldValue const component fieldValue.component if (component) { return component } const type fieldValue.type const cubeType cube-${type} if (components[cubeType]) { return cubeType } return type }即优先使用自定义component否则按cube-{type}匹配内置组件表components.js 中注册了 cube-button、cube-checkbox、cube-checkbox-group、cube-checker、cube-input、cube-radio、cube-radio-group、cube-rate、cube-select、cube-switch、cube-textarea、cube-upload 共 12 个组件匹配不到则直接按type作为组件名解析。submit与reset两个特殊类型会在字段预处理阶段被转换为按钮。见 types.js 与 props.js// src/components/form/fields/props.js const toButtonHandler (field, type) { field.type button if (!field.props) { field.props {} } field.props.type type }这也是文档中它们两个会被转换为对应类型的button的源码依据。转换后的按钮在form-item.vue中以cube-button渲染点击后触发原生 form 的 submit / reset 行为。三、自定义使用component 与插槽默认渲染方式不能满足所有场景时cube-form 允许两种自定义途径指定component使用自定义组件以及通过插槽完全自定义结构。3.1 使用实现 v-model 的自定义组件通过字段的component属性指定一个实现了v-model的自定义组件即可替换默认渲染。官方示例中的PCA组件是一个省市县三级联动选择器基于$createCascadePicker创建代码如下// province, city, area // select component const PCA { props: { value: { type: Array, default() { return [] } } }, data() { return { selected: [] } }, render(createElement) { return createElement(cube-button, { on: { click: this.showPicker } }, this.selected.length ? this.selected.join( ) : placeholder) }, mounted() { this.picker this.$createCascadePicker({ title: PCA Select, data: cityData, selectedIndex: this.value, onSelect: this.selectHandler }) }, methods: { showPicker() { this.picker.show() }, selectHandler(selectedVal, selectedIndex, selectedTxt) { this.selected selectedTxt this.$emit(input, selectedVal) } } }关键点在于组件通过$emit(input, ...)与父级完成双向绑定cube-form-item内部正是通过v-modelmodelValue的方式与之对接见 form-item.vue。3.2 插槽自定义结构cube-form、cube-form-group、cube-form-item 都提供了默认插槽。当cube-form-item内部有自定义插槽内容时字段渲染会退化为你自定义的内容例如在表单项里直接放一个按钮触发日期选择器cube-form :modelmodel validatevalidateHandler submitsubmitHandler cube-form-group cube-form-item :fieldfields[0]/cube-form-item cube-form-item :fieldfields[1]/cube-form-item cube-form-item :fieldfields[2] cube-button clickshowDatePicker{{model.dateValue || Please select date}}/cube-button date-picker refdatePicker :min[2008, 8, 8] :max[2020, 10, 20] selectdateSelectHandler/date-picker /cube-form-item /cube-form-group cube-form-group cube-button typesubmitSubmit/cube-button /cube-form-group /cube-formexport default { data() { return { validity: {}, valid: undefined, model: { inputValue: , pcaValue: [], dateValue: }, fields: [ { type: input, modelKey: inputValue, label: Input, props: { placeholder: 请输入 }, rules: { required: true } }, { component: PCA, modelKey: pcaValue, label: PCASelect, rules: { required: true }, messages: { required: 请选择 } }, { modelKey: dateValue, label: Date, rules: { required: true } } ] } }, methods: { submitHandler(e) { console.log(submit) }, validateHandler(result) { this.validity result.validity this.valid result.valid console.log(validity, result.validity, result.valid, result.dirty, result.firstInvalidFieldIndex) }, showDatePicker() { this.$refs.datePicker.show() }, dateSelectHandler(selectedVal) { this.model.dateValue new Date(selectedVal[0], selectedVal[1] - 1, selectedVal[2]).toDateString() } }, components: { DatePicker } }注意这里第三个字段dateValue没有指定 type 或 component它完全依靠插槽内容来渲染同时仍然参与表单的校验与数据绑定——这正体现了插槽自定义结构、字段定义负责校验与数据的分层设计。从实现看cube-form-item的模板里插槽默认渲染component :iscomponentName v-modelmodelValue v-bindfieldValue.props v-onfieldValue.events当插槽被填充时则完全使用插槽内容而校验容器cube-validator包裹在整个字段外层见 form-item.vue所以无论怎么自定义校验逻辑都由 Validator 统一接管。四、问卷场景利用 Form 特性构建动态表单cube-form 的数据驱动特性非常适合构建问卷类动态表单。仓库中提供了完整的问卷示例组件位于 example/components/questionnaire其核心思路是用一套 JSON 配置描述所有问题然后通过 transform 管道把配置转换成 cube-form 的 schema。使用方式demo-questionnaire :tiptip :questionsquestions :submitsubmit submitsubmitHandler /import DemoQuestionnaire from example/components/questionnaire/questionnaire.vue export default { data() { return { tip: 请配合如实填写问卷确保xxxx相关文案, questions: [ { type: switch, model: switch, title: 询问是否 // required: true }, { type: input, model: input, title: 输入, options: { placeholder: 请输入 }, on: switch, required: true }, { type: date, model: date, title: 日期, options: { // min: 2020-01-01, // max: 2020-02-18 }, required: true }, { type: time, model: time, title: 时间, options: { min: 01:00, max: 23:59 }, required: true }, { type: select, model: select, title: 选择, options: [ option1, option2, option3 ], required: true }, { type: radio, model: radio, title: 单选, options: [ 单选1, 单选2, 单选3 ], required: true }, { type: checkbox, model: checkbox, title: 多选, options: [ 多选1, 多选2, 多选3 ], required: true }, { type: textarea, model: textarea, title: 多行文本, on: { model: checkbox, options: [多选1, 多选3] }, required: true }, { type: checkbox, row: true, model: checkbox2, title: 多选-横, options: [ 多选-横1, 多选-横2, 多选-横3 ], required: true }, { type: tel, model: tel, title: 手机号, options: { placeholder: 请输入手机号 }, required: true }, { type: rate, model: rate, title: 级别, options: { max: 10 }, required: true }, { type: city, model: city, title: 城市, required: true }, { type: upload, model: upload, title: 上传, options: { action: //jsonplaceholder.typicode.com/photos/, max: 2 }, required: true }, { type: agreement, model: agreement, options: { text: 请同意, link: { text: 《xx协议》, href: https://github.com/didi/cube-ui }, desc: 说明本人承诺xx xxxxx xxx xx。 }, required: true, errMsg: 请同意协议 } ], submit: { text: Submit } } }, components: { DemoQuestionnaire }, methods: { submitHandler(model) { console.log(submit, model) } } }4.1 transform 管道配置到 schema 的转换问卷配置并不直接是 schema而是经过 transform/index.js 中的一系列转换函数处理export default function transform(config) { const field {} const transforms [ transformType, transformModel, transformTitle, transformOptions, transformRequired, transformErrMsg, transformCustom ] transforms.forEach((transformFn) { transformFn(config, field) }) return field }其中transformTypetype.js把问卷语义的type映射到 cube-form 的实际组件const componentMap { switch: radio-group, // 是 否 date: Select, time: Select, select: Select, city: Select, radio: radio-group, checkbox: checkbox-group, tel: input, agreement: Agreement } export default function transformType(config, field) { const realComponent componentMap[config.type] || config.type field[typeof realComponent string ? type : component] realComponent }可以看到问卷里的switch被渲染为radio-group是与否两个选项、date/time/select/city复用Select组件、tel复用input、checkbox渲染为checkbox-group而agreement则使用自定义的Agreement组件agreement.vue。4.2 条件显示on 依赖问卷支持通过on实现题目之间的条件关联只有前置题目满足条件时才渲染该题。以 textarea 题为例只有当checkbox多选的取值命中[多选1, 多选3]之一时才展示。该逻辑在 questionnaire.vue 的eachQuestion方法中实现on为字符串时等价于{ model: on }依赖该 model 值是否为真on为对象时支持modeloptions组合判断数组值做交叉判断任一命中即可非数组值做包含判断。问卷组件内部还统一初始化了model的默认值checkbox 与 upload 初始化为[]并在渲染时通过submitAlwaysValidate: true强制提交时校验全部字段见 questionnaire.vue。五、Props 配置详解5.1 CubeForm| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | model | 数据源 | Object | - | {} | | schema | 生成表单依赖的模式 | Object | - | {} | | immediateValidate | 初始化时是否立即校验 | Boolean | true/false | false | | action | 表单 Form action 的值 | String | - | undefined | | options | 配置项 | Object | - |{ scrollToInvalidField: false, layout: standard }| | submitAlwaysValidate | 提交表单时是否总校验所有字段1.12.36 | Boolean | true/false | false |5.2 schema 子配置项模式用于定义表单中的各个字段可以选择是否分组。无分组直接包含fields即可{ fields: [ { type: input, modelKey: inputValue, label: Input }, // ... ] }有分组设置groups每组可用legend指定分组名{ groups: [ { legend: Group 1, fields: [ { type: input, modelKey: inputValue, label: Input }, // ... ] }, { legend: Group 2, fields: [ { type: input, modelKey: inputValue, label: Input }, // ... ] } ] }无论是否分组都需要使用fields定义表单字段其中每一项的属性如下| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | type | 字段类型 | String | 内置组件button,checkbox,checkbox-group,input,radio,radio-group,rate,select,switch,textarea,upload特殊的submit和reset会被转换为对应类型的button| - | | component | 字段使用的自定义组件替换 type需实现v-model| Object/String | - | - | | modelKey | 在model数据源对象中对应的 key | String | - | - | | label | 字段的标签值 | String | - | - | | props | type 对应组件或自定义组件需要的 props | Object | - | - | | events | type 对应组件或自定义组件的事件回调1.8.0 | Object | - | - | | rules | 字段的校验规则参见 Validator | Object | - | - | | trigger | 若设置为blur则在离焦后校验1.8.0 | String | blur/change | - | | debounce | 控制校验节奏单位 ms若trigger为blur则此项不生效1.8.0 | Number/Boolean | 0设置为true时为 200ms | - | | messages | 字段的校验消息参见 Validator | String | - | - | | key | 字段的唯一 key尤其适用于 schema 更新的场景1.12.36 | String | - | - |5.3 options 子配置项| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | scrollToInvalidField | 是否滚动到第一个无效字段位置 | Boolean | true/false | false | | layout | 表单布局方式 | String | standard/classic/fresh | standard |5.4 CubeFormGroup| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | legend | 分组名字 | String | - | | | fields | 该组内所包含的字段集合 | Array | - | [] |5.5 CubeFormItem| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | field | 字段数据 | Object | - | - |5.6 内置类型的特殊处理type处理并非简单的组件映射在 types.js 中部分类型的required规则会被注入针对性的默认实现这是 cube-form 为开箱即用做的隐性工作| 类型 | 处理逻辑 | 说明 | | - | - | - | | submit / reset | 转换为 type 为 button并注入props.type submit / reset| 见 props.js | | checkbox / switch | required 规则被替换为val val ! false| 布尔型false视为不通过 | | rate | required 规则被替换为val val 0| 数值型大于 0 才通过 |对应实现见 validate.js这解释了为什么示例中 checkbox/switch/rate 只需声明rules: { required: true }即可达到必须勾选/必须打星的语义。5.7 字段重置的默认值reset事件的字段重置逻辑在 reset.js 中定义不同类型会重置为不同的默认值const typesResetMap { checkbox() { return false }, select() { return null } }即 checkbox 重置为false、select 重置为null其余类型则由通用工具resetTypeValue处理。具体实现位于 form-item.vue重置时还会同步 validator 值因为可能存在 trigger: blur 或 debounce 的延迟校验。六、事件机制| 事件名 | 说明 | 参数1 | 参数2 | 参数3 | | - | - | - | - | - | | submit | 表单校验通过后触发只有同步校验时不阻止默认行为包含异步校验则默认阻止默认行为 | e - 事件对象 | model 值 | 只包含存在的字段的 model 值1.12.30 | | reset | 表单重置事件 | e - 事件对象 | - | - | | validate | 表单校验事件 | 参数结构见下 | - | - | | valid | 表单校验成功触发 | validity 校验结果 | - | - | | invalid | 表单校验失败触发 | validity 校验结果 | - | - |validate事件参数结构| 参数 | 说明 | 类型 | | - | - | - | | validity | 校验结果 | Object | | valid | 校验合法还没校验为 undefined一旦校验则为 true 或 false | Boolean/Undefined | | invalid | 校验不合法还没校验为 undefined一旦校验则为 true 或 false | Boolean | | dirty | 表单处于 dirty 状态数据源发生了变化 | Boolean | | firstInvalidFieldIndex | 第一个校验不合法的字段索引值 | Number |6.1 校验结果 validity 对象| 参数 | 说明 | 类型 | | - | - | - | | valid | 校验是否合法 | Boolean/Undefined | | result | 校验结果形如{ required: { valid: false, invalid: true, message: Required. } }| Object | | dirty | 数据是否已经更新过 | Boolean |6.2 事件在源码中的触发路径validate由validatedCount的 watcher 触发聚合validity / valid / invalid / dirty / firstInvalidFieldIndex后$emit见 form.vuevalid/invalid/submit在submitHandler中根据校验结果分发见 form.vuereset由resetHandler触发先执行_reset()重置所有字段再 emit见 form.vue。值得强调的是valid的三态语义由于支持异步校验valid在尚未校验和正在校验/挂起时都可能是undefined。从 mixin.js 可以看到valid() { const originValid this.originValid const pending this.pending const validating this.validating return (pending || validating) ? undefined : originValid }, invalid() { const valid this.valid return valid undefined ? valid : !valid }当pending防抖等待中或validating异步校验进行中为真时valid返回undefined这也是为什么文档中valid的合法值包含Boolean/Undefined两种。6.3 submit 的默认行为细节submitHandler的完整逻辑form.vue说明了文档中如果只有同步校验则不会阻止默认行为如果包含了异步校验则默认就会阻止默认行为的机理提交前先syncValidatorValues()同步所有字段值应对 blur/debounce 延迟场景若submitAlwaysValidate为 true 或valid undefined从未校验过则重新执行完整校验并在validating || pending时调用e.preventDefault()阻止原生提交校验失败时若配置了scrollToInvalidField会自动调用firstInvalidField.$el.scrollIntoView()滚动到第一个无效字段然后preventDefault()并触发invalid事件校验通过时触发valid与submit事件submit事件携带e / this.model / this.fieldsModel三个参数其中fieldsModel只包含 schema 中声明了modelKey的字段值。6.4 实例方法| 方法名 | 说明 | 参数 | 返回值 | | - | - | - | - | | submit | 提交表单 | skipValidate默认 false为 true 时不校验直接 submit1.12.2 | - | | reset | 重置表单 | - | - | | validate(cb) | 校验表单 | cb校验完成后回调参数为 valid 的值 | 支持 Promise 时返回 Promise仅 resolved 状态值为 valid否则 undefined |submit方法通过dispatchEvent(this.$refs.form, submit)派发原生 submit 事件来触发内部submitHandler见 form.vuevalidate方法内部采用并发校验所有字段、全部完成后回调的策略form.vue异步规则并行执行后统一汇总结果。七、校验触发时机change / blur / debounce 的取舍从 1.8.0 起cube-form 支持三种校验节奏控制这也是官方文档强调的升级点| 触发方式 | 配置 | 行为 | 适用场景 | | - | - | - | - | | change默认 | 不配置 trigger | 每次数据变化立即校验 | 需要实时反馈的短表单 | | blur |trigger: blur| 离焦时才校验 | 输入型字段避免输入过程中频繁提示 | | debounce |debounce: ms| 停止输入 N 毫秒后校验true时为 200ms | 输入型字段的节流校验 |源码实现上form-item.vue配置debounce时通过debounce()工具函数包装校验逻辑延迟指定毫秒后执行配置trigger: blur时在form-item根节点上监听focusin/focusout事件聚焦时不更新校验模型离焦时才同步值并执行校验debounce与trigger: blur互斥debounce配置在 blur 场景下不生效this.fieldValue.trigger blur时直接 return。需要注意无论哪种触发方式最终校验都交给cube-validatorValidator 组件执行校验规则与异步支持与 Validator 完全一致。八、总结cube-form 的价值在于把表单渲染与数据维护彻底解耦开发者用model描述数据、用schema描述结构与规则组件负责渲染、回写、校验、重置与提交事件的全链路。配合component与插槽机制它能覆盖从标准表单、自定义组件表单到复杂问卷场景的绝大多数移动端表单需求。仓库中还提供了完整的单元测试test/unit/specs/form.spec.js覆盖组件注册、分组渲染、校验与重置、触发时机等行为以及 example/pages/form 目录下的 default / classic / fresh / custom / questionnaire 五个可运行示例页面可以直接在示例工程中运行体验三种布局与自定义场景的实际效果。赞分享前端UI组件移动开发【免费下载链接】cube-ui:large_orange_diamond: A fantastic mobile ui lib implement by Vue项目地址https://gitcode.com/gh_mirrors/cu/cube-ui点击查看免费下载相关推荐cube-ui 数据驱动表单组件 CubeForm 完整实战指南schema 配置、校验事件与自定义扩展cube ui 数据驱动表单组件 CubeForm 完整实战指南schema 配置、校验事件与自定义扩展 CubeForm 是 cube ui 在 1.7.0前端UI组件移动开发Naive UI Form 表单组件完全指南数据收集、规则校验与布局实战Naive UI Form 表单组件完全指南数据收集、规则校验与布局实战 Naive UIA Vue 3 Component Library的 Form前端UI组件antd Form 表单组件完全指南数据域管理、校验与动态表单实战基于 ant-design 组件库antd Form 表单组件完全指南数据域管理、校验与动态表单实战基于 ant design 组件库 本指南以 ant design 仓库中 Form 官前端UI组件设计系统上一篇终极指南如何用FanControl风扇控制软件告别电脑噪音烦恼下一篇Material-Kit-React 终极图标指南Phosphor Icons 完整使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表