前言:
此时大家对“react select联动”大致比较重视,小伙伴们都想要了解一些“react select联动”的相关资讯。那么小编同时在网络上收集了一些关于“react select联动””的相关知识,希望我们能喜欢,你们一起来学习一下吧!调研
最近收到了一个任务,需要实现一个动态的表单。效果如下:
图一图二
「主要逻辑」:第一个下拉框的数据能决定第二个下拉框的内容,第二个下拉框的内容是远程加载的;第三个下拉框的内容能决定后续控件的形式;后面的加号是增加行,括号是增加括号,删除是删除这一行。最后的检索式,是整体表单计算出来的。
「分析」:这个表单逻辑和联动都比较复杂和频繁,而且有些还是远程获取数据,并不是写死的。如果说用elementplus的组件来做,处理逻辑、联动和数据回显的时候都会比较麻烦,肯定不简洁。我之前有做过一个小型的复杂表单联动,有过这种手动处理逻辑和联动的麻烦经历。所以我就决定不使用之前的方式处理现在的业务,需要寻找一个新的方式来解决这个需求。
「方案」:在这里我就省去了找解决方案的过程,直接说答案,最后是决定使用Formily。原因有几个:
大厂出品:后续有不会的地方,能在网上搜得到生态丰富:涵盖了vue和react,elementui、elementplus、antd等不同的版本了解
我此次负责的项目的技术栈是Vue3,所以我主要去了解了以下几个模块:
Fromily主站Formily Vue核心库Formily elementplus「主站文档」首先是对Formily进行了解释,说明了Formily这个产品的定位和功能,其次的「场景案例」以及「进阶指南」的代码实例非常友好(案例使用react写的,使用vue的时候有一些差别)。读完此文档后对于Formily有了一定的了解;除此之外,主站中的API内容在后续实践中比较重要,也需要熟悉。「Vue核心库」中,讲解了核心架构以及核心概念,核心概念中最重要的是三种开发模式:Template 开发模式该模式主要是使用 Field/ArrayField/ObjectField/VoidField 组件
<template> <FormProvider :form="form"> <Field name="input" :component="[Input, { placeholder:'请输入' }]" /> </FormProvider></template><script> import { Input } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/vue' import 'ant-design-vue/dist/antd.css' export default { components: { FormProvider, Field }, data() { return { Input, form: createForm(), } }, }</script>
JSON Schema 开发模式
该模式是给 SchemaField 的 schema 属性传递 JSON Schema 即可
<template> <FormProvider :form="form"> <SchemaField :schema="schema" /> </FormProvider> </template> <script> import { Input } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import 'ant-design-vue/dist/antd.css' const { SchemaField } = createSchemaField({ components: { Input, }, }) export default { components: { FormProvider, SchemaField }, data() { return { form: createForm(), schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', 'x-component-props': { placeholder: '请输入', }, }, }, }, } }, } </script>
Markup Schema 开发模式该模式算是一个对源码开发比较友好的 Schema 开发模式,同样是使用 SchemaField 相关组件。Markup Schema 模式主要有以下几个特点:主要依赖 SchemaStringField/SchemaArrayField/SchemaObjectField...这类描述标签来表达 Schema每个描述标签都代表一个 Schema 节点,与 JSON-Schema 等价SchemaField 子节点不能随意插 UI 元素,因为 SchemaField 只会解析子节点的所有 Schema 描述标签,然后转换成 JSON Schema,最终交给RecursionField渲染,如果想要插入 UI 元素,可以在 SchemaVoidField 上传x-content属性来插入 UI 元素
<template> <FormProvider :form="form"> <SchemaField> <SchemaStringField x-component="Input" :x-component-props="{ placeholder: '请输入' }" /> <div>我不会被渲染</div> <SchemaVoidField x-content="我会被渲染" /> <SchemaVoidField :x-content="Comp" /> </SchemaField> </FormProvider></template><script> import { Input } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import 'ant-design-vue/dist/antd.css' const SchemaComponents = createSchemaField({ components: { Input, }, }) const Comp = { render(h) { return h('div', ['我也会被渲染']) }, } export default { components: { FormProvider, ...SchemaComponents }, data() { return { form: createForm(), Comp, } }, }</script>
「Formily elementplus」
这个就类似于组件库,讲解具体组件如何使用。
熟悉
这个阶段熟悉了一些官网案例的用法。在三种使用模式中,最后选择了「JSON Schema」模式,这种模式组件看着更简洁,只需掌握配置规则。
运用因为 Element-Plus 是基于 Sass 构建的,如果你用 Webpack 配置请使用以下两个 Sass 工具
"sass": "^1.32.11","sass-loader": "^8.0.2"
安装
$ npm install --save element-plus$ npm install --save @formily/core @formily/vue @vue/composition-api @formily/element-plus
我的目录结构是这样:
核心的Formily代码在「filter.vue』中,JSON配置我提炼到了「form_obj.js」里
filter.vue:
<template> <FormProvider :form="form" class="lkkkkkkk"> <SchemaField :schema="schema" :scope="{ useAsyncDataSource, loadData }" /> <div class="btn flex flex-right"> <div class="btn-inner"> <el-button type="primary" plain :disabled="valid" @click="saveFilter">保存</el-button> <el-button type="primary" plain @click="resetFilter">重置</el-button> <Submit plain @submit-failed="submitFailed" @submit="submit">查询</Submit> </div> </div> </FormProvider></template><script setup> import { createForm } from '@formily/core'; import { FormProvider, createSchemaField } from '@formily/vue'; import { Submit, FormItem, Space, Input, Select, DatePicker, ArrayItems, InputNumber, } from '@formily/element-plus'; import conditionResult from './conditionResult.vue'; import { onMounted, ref } from 'vue'; import { action } from '@formily/reactive'; import { getFormObj, arrToText } from './form_obj'; import { setLocal, getLocal } from '@/utils'; const { SchemaField } = createSchemaField({ components: { FormItem, Space, Input, Select, DatePicker, ArrayItems, InputNumber, conditionResult, }, }); const form = createForm(); const schema = ref(); const fieldMap = new Map(); const valid = ref(true); const fieldCollect = (arr) => { arr.forEach((item) => { fieldMap.set(item.value, item); }); }; // 模拟远程加载数据 const loadData = async (field) => { const table = field.query('.table').get('value'); if (!table) return []; return new Promise((resolve) => { setTimeout(() => { if (table === 1) { const arr = [ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]; resolve(arr); fieldCollect(arr); } else if (table === 2) { const arr = [ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]; resolve(arr); fieldCollect(arr); } }, 1000); }); }; // 远程数据处理 const useAsyncDataSource = (service) => (field) => { field.loading = true; service(field).then( action.bound((data) => { field.dataSource = data; field.loading = false; }) ); }; // 获取表数据 const getTables = () => { return new Promise((resolve) => { setTimeout(() => { resolve([ { label: '就诊信息表', value: 1 }, { label: '诊断信息表', value: 2 }, ]); }, 1000); }); }; // 初始化表单 const initForm = async () => { const tables = await getTables(); schema.value = getFormObj(tables); const originFilter = getLocal('formily'); // 设置初始值或者是回显值 if (originFilter) { form.setInitialValues(originFilter); // form.setInitialValues({ // array: [ // { // table: 1, // field: '', // condition: 'contain', // text: '', // relationship: 'none', // bracket: 'none', // }, // ], // escape: '', // }); } }; // 保存 const saveFilter = () => { setLocal('formily', form.values); }; // 重置 const resetFilter = () => { form.setValues(getLocal('formily')); }; // 查询 const submit = (values) => { // 将数组转换成中文释义。 const sentence = arrToText(fieldMap, values.array); console.log(sentence); // 将值设置到检索式中 form.setValuesIn('escape', sentence); valid.value = false; }; const submitFailed = () => { valid.value = true; }; onMounted(() => { initForm(); });</script>
form_obj.js:
...// Formily配置export function getFormObj(tables) { return { type: 'object', properties: { array: { type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', title: '检索条件', items: { type: 'object', properties: { space: { type: 'void', 'x-component': 'Space', properties: { sort: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.SortHandle', }, table: { type: 'string', title: '信息表', enum: tables, required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: '160px', }, }, }, field: { type: 'string', title: '字段', required: true, // default: 1, // enum: [ // { label: '入院年龄', value: 1 }, // { label: '主要诊断', value: 2 }, // { label: '手术名称', value: 3 }, // ], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: '160px', }, }, 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], }, condition: { type: 'string', title: '条件', required: true, // default: 'contain', enum: conditionArr, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: '130px', }, }, }, text: { type: 'string', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-reactions': [ { dependencies: ['.condition'], fulfill: { state: { visible: "{{$deps[0] === 'contain'}}", }, }, }, ], 'x-component-props': { style: { width: '160px', }, placeholder: '请选择', }, }, range: { type: 'object', properties: { space: { type: 'void', 'x-component': 'Space', properties: { start: { type: 'number', required: true, 'x-reactions': `{{(field) => { field.selfErrors = field.query('.end').value() <= field.value ? '左边必须小于右边' : '' }}}`, // default: 1, 'x-decorator': 'FormItem', 'x-component': 'InputNumber', 'x-component-props': { style: { width: '150px', }, placeholder: '左临界数值', }, }, end: { type: 'number', required: true, // default: 10, 'x-decorator': 'FormItem', 'x-component': 'InputNumber', 'x-reactions': { dependencies: ['.start'], fulfill: { state: { selfErrors: "{{$deps[0] >= $self.value ? '左边必须小于右边' : ''}}", }, }, }, 'x-component-props': { style: { width: '150px', }, placeholder: '右临界数值', }, }, }, }, }, 'x-reactions': [ { dependencies: ['.condition'], fulfill: { state: { visible: "{{$deps[0] === 'range'}}", }, }, }, ], }, relationship: { type: 'string', title: '关系', required: true, // default: '', enum: relationArr, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: '160px', }, }, }, bracket: { type: 'string', title: '括号', required: true, // default: '', enum: bracketArr, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: '130px', }, }, }, copy: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.Copy', }, remove: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.Remove', }, }, }, }, }, properties: { add: { type: 'void', title: '添加条目', 'x-component': 'ArrayItems.Addition', }, }, }, // properties: { // }, escape: { type: 'string', title: '检索式', 'x-component': 'conditionResult', 'x-decorator': 'FormItem', }, }, };}...
此案例的完整代码,我放在我的github了,有需要自取。
延伸
完成这个案例之后,后续还有一个类似的表单需求,我本来是准备用这个来做的,但是这个需求是要放到IE上运行,所以我留了一个心眼。先写了一个小案例,测试了一下Vue2+elementui+Formily打包后在IE浏览器能否运行,最后发现是不可以。
后续再查资料中发现确实是不兼容IE的,大家在使用的时候要考虑这个场景。
总结
以上就是在Vue3中引入Formily解决需求的过程,经历了调研、了解、熟悉、运用的过程,Formily是一个比较好的表单处理工具,解决了表单联动、逻辑处理和回显的痛点,如果大家遇到此类需求,可以考虑一下使用这个工具,但是此工具不兼容IE的情况也要考虑进去。「吐槽一句就是,Formily文档写得其实不是很明朗」
作者:李仲轩
链接:
标签: #react select联动