我正在参加「掘金·启航计划」
前言
最近了解了一下 formily 这个库,formily 是一个支持 react 和 vue 项目的表单管理的一个库,非常强大。底层是对表单数据用 es6 的 Proxy 代理,对数据进行劫持来做发布订阅,来达到更好的性能,正因为用了 Proxy 这个 api,所以是不支持 IE 浏览器的,作者也表示放弃 IE 了,作者演讲formily的一部分视频。
四个核心库:
组件生态:
支持目前主流的 UI 组件库,对组件库里的表单组件做了桥接和 props 映射,使用的话需要去看 formily 的这些组件文档。
- @formily/antd
- @formily/next
- @formily/element
- @formily/element-plus
- @formily/antdv
- @formily/vant
- @formily/semi
- @formily/tdesign-react
- aliyun teamix
- antd-formily-boost
formily 创建表单的三种方式
-
Markup Schema
- 标注schema 和 JSON Schema 相似,只是把 JSON 格式转换成类似 JSX 的方式,可能比 JSON Schema 书写起来更好扩展,还不太了解这种写法的使用场景
-
JSON Schema
- 使用 JSON-Schema 协议来描述一个表单的字段,组件以及联动等,还在 JSON-Schema 的基础上加上了一些扩展属性,以
x-*格式来表达。
- 使用 JSON-Schema 协议来描述一个表单的字段,组件以及联动等,还在 JSON-Schema 的基础上加上了一些扩展属性,以
-
纯 JSX
- 和 react JSX 写法完全一样
表单联动:
表单中最复杂的可能就是一些联动逻辑,可以看看在 fromily 中如何实现表单联动。
- 在 JSON schema 中实现表单联动逻辑,通过 x-reactions 属性配置
- 在 createForm 的时候做联动,通过传入 effects 方法
简单的联动案例
import React from 'react';
import { createForm, onFieldReact, onFieldChange, registerValidateLocale, setValidateLanguage } from '@formily/core';
import { createSchemaField } from '@formily/react';
const DemoForm = createForm({
validateFirst: true, // 只展示第一个校验错误提示
validateLanguage: 'en-US', // 校验提示展示的语言
initialValues, // 表单初始值
effects(form) {// 做一些表单的复杂逻辑
//主动联动模式
// onFieldChange('size', ['items.*.url'], (field, form) => {
// // field 和 props 中有很多熟悉和 api,可以看官网文档去做一些联动逻辑
// console.log(123, field)
// })
//被动联动模式
// onFieldReact('items.*.url', (field) => {
// // field.query('size').get('value') 表示查询表单中 size 字段的值
// // 还可以通过 FormPath 的方式获取, 参考文档 https://core.formilyjs.org/zh-CN/api/entry/form-path
// // field.query('.aa').value() //直接读取同级别aa字段值
// // field.query('..aa').value() //读取父级aa字段值
// // field.query('..[+].aa') //读取跨级相邻aa字段值
// const isSmall = field.query('size').get('value') == 2;
// const width = isSmall ? 250 : 720;
// const height = isSmall ? 300 : 240;
// // 设置组件的 props
// field.setComponentProps({
// outputImgSize: {
// width,
// height,
// maxFileSize: 4,
// },
// cropPreview: isSmall ? 'imgPreview' : '',
// cropper: {
// aspectRatio: width / height,
// background: false,
// responsive: true,
// autoCropArea: 1,
// zoomable: false,
// }
// })
// })
}
});
const formItemLayout = {
labelCol: 5,
wrapperCol: 19,
labelAlign: 'left',
};
const schema = {
type: 'object', // 对象类型
properties: { // 表单中的字段
size: { // 表单字段key
type: 'number', // 字段值的类型
title: 'Image Size', // 字段的 label
required: true, // 内置校验,必填
enum: [ // 该组件的属性,radio 的选项
{
label: 'Big (720px*240px)',
value: 1,
},
{
label: 'Small (250px*300px)',
value: 2,
},
],
'x-decorator': 'FormItem', // 组件包裹的组件,一般为 formItem
'x-component': 'Radio.Group', // 渲染的组件
},
items: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
// 自定义校验,return 出去的表示校验不通过的提示
'x-validator': `{{(value)=> {
if(value.length > 10) {
return 'Configure up to 10';
}
if(value.length < 1) {
return 'Configure at least one';
}
}}}`,
items: {
type: 'object',
properties: {
url: {
type: 'string',
title: 'Image',
required: true,
'x-decorator': 'FormItem',
'x-component': 'CropImageUploader',
'x-component-props': { // 组件 props
outputImgSize: {
width: 720,
height: 240,
maxFileSize: 4,
},
cropPreview: '',
cropper: {
aspectRatio: 720 / 240,
background: false,
responsive: true,
autoCropArea: 1,
zoomable: false,
},
},
'x-decorator-props': { // 组件包裹的组件 props
...formItemLayout,
},
'x-reactions': [ // 组件联动
{
dependencies: ['size'], // 依赖项
// $deps[0] 表示依赖数组中的第一项,双花括号中的是表达式
when: '{{$deps[0] != 2}}',
fulfill: { // 满足 when 表达式做 fulfill 中的逻辑
// state: { // 该组件的 state
// // state 中有很多熟悉,不止组件的 props,还有控制组件的现实和隐藏,具体看官网的文档
// 'component[1].outputImgSize': { // 修改组件props属性的值
// width: 720,
// height: 240,
// maxFileSize: 4,
// },
// 'component[1].cropPreview': '',
// 'component[1].cropper.aspectRatio': 720 / 240,
// },
// 上面的写法和下面的效果是一样的
schema: {
'x-component-props.outputImgSize': {
width: 720,
height: 240,
maxFileSize: 4,
},
'x-component-props.cropPreview': '',
'x-component-props.cropper.aspectRatio': 720 / 240,
},
},
otherwise: { // 不满足 when 表达式的做 otherwise 中的逻辑
// state: {
// 'component[1].outputImgSize': {
// width: 250,
// height: 300,
// maxFileSize: 4,
// },
// 'component[1].cropPreview': 'imgPreview',
// 'component[1].cropper.aspectRatio': 250 / 300,
// },
schema: {
'x-component-props.outputImgSize': {
width: 250,
height: 300,
maxFileSize: 4,
},
'x-component-props.cropPreview': 'imgPreview',
'x-component-props.cropper.aspectRatio': 250 / 300,
},
},
},
],
},
sort: {
type: 'string',
title: 'Sorting',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'please enter sorting',
},
'x-decorator-props': {
...formItemLayout,
},
// 自定义校验中也可以写正则表达式
'x-validator': `{{(value)=> {
const reg = new RegExp(/^(0|([1-9]+\\d*))$/, 'g');
if(!reg.test(value)) {
return 'Please enter Resource ID and must be integer';
}
}}}`,
},
remove: {
type: 'void', // 表示该组件为空值,只做UI展示,这里是对 ArrayItems 的删除按钮
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
properties: {
add: { // 新增 ArrayItem 的按钮
type: 'void',
title: 'add new Type',
'x-component': 'ArrayItems.Addition',
},
},
},
},
};
表单校验
- 通过 api 配置校验
- JSON schema 通过 x-validator 设置自定义校验,也可以通过 x-reactions 配置去实现联动校验,具体看官网文档
表单布局
布局可以使用 FormLayout FormItem FormGrid Space 这四个组件自由搭配使用,配置 type 为 void 表示空值字段,下面配置是简单实现了表单中一行显示两个 formItem 布局。
const schema = {
type: 'object',
properties: {
grid: {
type: 'void', // 表示空字段
title: 'Brand Title', // formItem 的 label
'x-component': 'Space',
'x-decorator': 'FormItem',
'x-decorator-props': {
asterisk: true, // label 上显示必填的 * 号
feedbackLayout: 'none',
...formItemLayout,
},
properties: {
nameEn: {
type: 'string',
title: '',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'In English',
addonTextBefore: 'En: ',
maxLength: 32,
showLimitHint: true,
},
},
nameLocal: {
type: 'string',
title: '',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'In local language',
addonTextBefore: 'Local: ',
maxLength: 32,
showLimitHint: true,
},
},
}
}
},
};
效果:
总结
本次使用 formily 只了解到了这个库一部分的使用方法,个人感觉基本可以满足百分之八九十的表单交互使用,很多功能需要通过官网慢慢去了解,参考官网的一些案例,因为它的字段熟悉和 api 太多了,所以需要一点时间。配置过程中遇到个问题感觉比较坑,使用 FormGrid 组件,type 类型本应该是 void,手快打错成了 viod,导致这个UI组件中的内容也没有显示,页面也没有报错,排查起来比较费时。