FormRender 2.0 开箱即用表单方案

分享 FormRender 2.0 开箱即用的表单解决方案,产品体验更上一层楼。今日前端早读课文章由 @斩鲌分享,公号:Fliggy FE 授权。

正文从这开始~~

在前端开发过程中,表单渲染是重要且繁琐的一环。为了提高开发效率并避免重复工作,飞猪推出了基于 React 的表单渲染器 FormRender。FormRender 使用 JsonSchema 协议渲染表单,是适用于中后台表单的一种通用解决方案。本文将介绍 FormRender 的基本概念、使用方式及高级特性。

图片

官方文档:xrender.fun/form-render

github:github.com/alibaba/x-r…

简介

  • FormRender 通过 JsonSchema 协议渲染表单,为中后台表单业务提供开箱即用的通用解决方案。
  • FormRender 是属于飞猪 XRender 系列的一款开源库,于 2023 年 2 月完成了版本升级并发布了相应的升级公告。当前最新版本可能已经不同,建议查看官网以获得最新信息。
  • FormRender 最近推出了适用于移动端 H5 表单的解决方案,如果你对此感兴趣,可以查看官方文档以获取详细信息。
优势
  • 协议简单:协议相对简单,并在一定程度上遵循了 JsonSchema 规范,因此相对容易上手和理解。
  • 较强的配置能力:具有较强的配置能力,可以对表单联动、校验、布局以及数据处理等方面进行配置。
  • 良好的性能体验:通过对 FormRender 进行重构,底层采用 Antd Form 来实现表单的数据收集和管控,同时针对控件渲染层面进行优化处理,从而大幅提升性能,使得在使用过程中具有良好的性能体验。
  • 内置组件丰富:内置组件非常丰富,包括基础组件、嵌套卡片类组件和动态增减 List 组件等,可以满足大多数场景的表单实现需求。
  • 扩展性强:具有非常强的扩展性,支持自定义各种类型的表单控件,用户可以根据实际需要进行定制,非常灵活。
  • 易于使用:容易上手,可以通过表单设计器可视化拖拽的方式快速生成表单。

1、安装依赖

npm install form-render --save

2、使用方式

import React from 'react';
 import FormRender, { useForm } from 'form-render';




 const schema = {
   type: 'object',
   properties: {
     input: {
       title: '输入框',
       type: 'string'
     },

     select: {
       title: '下拉框',
       type: 'string',
       props: {
         options: [
           { label: '早', value: 'a' },
           { label: '中', value: 'b' },
           { label: '晚', value: 'c' }
         ]
       }
     }
   }
 };



 export default () => {
   const form = useForm();


   const onFinish = (formData) => {
     console.log('formData:', formData);
   };

   return (
     <FormRender
       form={form}
       schema={schema}
       onFinish={onFinish}
       footer={true}
     />
   );
 }

image.png

3、通过可视化表单生成器 Schemabuilder ,用户可以拖拖拽拽快速生成 JsonSchema。

图片

4、通过 Playground 可以进行在线体验,并查看其中丰富的表单示例。

图片

高级特性

1. 表单校验

FormRender 简化了表单校验配置,常规校验可以通过协议内置字段进行配置,同时也支持像 Antd Rules 的配置方式,并且可以处理更复杂的子表单联动校验配置。

内置校验

为了简化校验逻辑的配置,FormRender 内置了一些校验配置选项,包括以下字段:

  • 必填:required
  • 最大长度、最大值:max
  • 最小长度、最小值:min
  • 字符串特殊格式:format,其中 format 的格式有 url、email、image、color
  • 正则表达式:pattern
 const schema = {


   "type": "object",


   "displayType": "row",
   "properties": {
     "input1": {
       "title": "必填",
       "type": "string",
       "widget": "input",
       "required": true
     },

     "input2": {
       "title": "数值大小",
       "type": "number",
       "widget": "inputNumber",
       "max": 5,
       "min": 1
     },
     "input4": {
       "title": "字符串长度",
       "type": "string",
       "widget": "input",
       "max": 20,
       "min": 5
     },
     "input6": {
       "title": "url 校验",
       "type": "string",
       "widget": "urlInput",
       "format": "url"
     }
   }
 }

Rules 校验

FormRender 的 Rules 校验规则可以参考 Antd Form Rules API,validator 自定义校验规则做了一点小的调整,自定义方法的返回值类型,改成布尔值或者对象格式

 const schema = {
   type: 'object',
   displayType: 'row',
   properties: {
     input2: {
       title: '自定义校验',
       type: 'string',
       rules: [
         {
           validator: (_, value) => {
             const pattern = '^[\u4E00-\u9FA5]+$';
             const result = new RegExp(pattern).test(value);
             return result;
             // 或者是返回一个对象,用于动态设置 message 内容
             // return {
             //   status: result,
             //   message: '请输入中文!',
             // }
           },

           message: '请输入中文!'
         }
       ]
     }
   }
 };

子表单校验

在处理非常复杂的表单场景时,有时会使用自定义组件作为子表单。在这种情况下,表单的提交行为无法触发子表单进行校验,因此需要对自定义子组件进行特殊处理。

子表单组件提供一个触发内部校验的函数,并通过 useImperativeHandle 暴露出去

 import { useImperativeHandle } form 'react';

 const ChildForm = (props) => {



    // 内部校验方法,异步校验请用 async、await 语法


     const validator = async () => {
         return true; // 返回 boolean 值,true 表示内部校验通过


         // 如果需在外部显示子表单错误信息可以使用对象形式返回
         // retrun { status: boolean, message: string };
     };

     useImperativeHandle(props.addons.fieldRef, () => {
         // 将校验方法暴露出去,方便外部表单提交时,触发校验
         return {
             validator
         };
     });


     return (
         ...// 表单渲染代码
     );
 }


 export default ChildForm;
2、表单联动

表单联动是前端开发中常见的一种需求,也是前端开发人员经常遇到的难点之一,往往评价一个表单渲染器能力强不强,表单联动能力至关重要。FormRender 通过函数表达式、依赖项关联、watch 监听 等方式尽可能的在表单联动上做的更加易用,降低表单联动的成本。

函数表达式

函数表达式为字符串格式,以双花括号 {{ ... }} 为语法特征,对于简单的联动提供一种简洁的配置方式。组件所有的 Schema 协议字段都支持函数表达式。

例如:控制表单项禁用、隐藏、文案提示等交互。

 {

   "type": "object",

   "properties": {



     "input": {
       "title": "输入框",
       "type": "string",

       "widget": "input",
       "hidden": "{{ formData.hidden }}",
       "disabled": "{{ formData.disabled }}",
       "placeholder": "{{ formData.placeholder || '请输入' }}"
     },
     "placeholder": {
       "title": "修改提示信息",
       "type": "string",
       "widget": "input"
     },
     "hidden": {
       "title": "隐藏",
       "type": "boolan",
       "widget": "switch"
     },
     "disabled": {
       "title": "禁用",
       "type": "boolan",
       "widget": "switch"
     }
   }
 }

formData 表示整个表单的值,rootValue 用于 List 的场景,表示 List.Item 的值。

除此之外,FormRender 还支持更加细粒度的配置,例如使用函数表达式来控制某个选项的行为。

 {

   "type": "object",

   "properties": {



     "input1": {
       "title": "当输入框的值为 2 时,下拉单选第二个选项隐藏,如果已选中并清空选中值",
       "type": "string",

       "widget": "input"
     },

     "select1": {
       "title": "下拉单选",
       "type": "string",
       "widget": "select",
       "defaultValue": "{{ (formData.input1 === '2' && formData.select1 === 'b') ? undefined : formData.select1 }}",
       "props": {
         "options": [
           {
             "label": "早",
             "value": "a"
           },

           {
             "label": "中",
             "value": "b",
             "hidden": "{{ formData.input1 === '2' }}"
           },
           {
             "label": "晚",
             "value": "c"
           }
         ]
       }
     }
   }
 }

watch 监听

Antd Form 通过 onValuesChange 事件来监听字段值的更新,而 FormRender 提供了一个功能更为强大的 API 来监听值的变化,即 watch API。下面让我们深入了解其用法。

 const watch = {
   // '#' 等同于 onValuesChagne
   '#': (allValues, changedValues) => {
     console.log('表单 allValues:', allValues);
     console.log('表单 changedValues:', changedValues);
   },
   'input': value => {
     console.log('input:', value);
   },
   // 监听 对象嵌套 场景,某个表单项的值发生变化
   'obj.input': (value) => {
     console.log('input:', value);
   },
   // 监听 List 场景,某个表单项的值发生变化
   'list[].input': (value, indexList) => {
     console.log('list[].input:', value, ',indexList:', indexList);
   }
 };

配合 form.setSchemaByPath 和 form.setValueByPath 就能实现更加复杂的表单联动需求。

dependencies 关联依赖项

当依赖项的值发生变化时,组件自身会触发更新和校验操作。在组件内部,可以通过 props.addons.dependValues 属性来获取依赖项的更新值。

一个经典的场景就是 “密码二次确认”,代码如下

 const schema = {
   type: 'object',
   displayType: 'row',
   properties: {
     input1: {
       title: '密码',
       type: 'string',
     },

     input2: {
       title: '确认密码',
       type: 'string',
       dependencies: ['input1'],
       rules: [
         {
           validator: (_, value, { form }) => {
             if (!value || form.getValueByPath('input1') === value) {
               return true;
             }
             return false;
           },
           message: '你输入的两个密码不匹配'
         }
       ]
     }
   }
 };

getFieldRef

在使用自定义组件时,可以通过 form.getFieldRef (‘path’) 方法获取到对应的组件实例。其中,path 是该组件的表单路径,当然自定义组件也要通过 props.addons.fieldRef 操作进行绑定。

 import { useImperativeHandle } form 'react'
 const CustomField = (props) => {




   useImperativeHandle(props.addons.fieldRef, () => {
     return {
       // 暴露内部方法
     };
   });


   return (
     ...// 组件渲染代码
   );
 }

 export default CustomField;
3、数据转换

在开发过程中,经常会遇到需要将前端数据转换为符合服务端数据结构的情况。为了解决这个问题,FormRender 提供了一个名为 bind 的魔法字段。通过在表单项中指定 bind 字段,可以将该表单项的值绑定到一个自定义的数据结构中,从而方便实现前后端数据格式的转换。

场景一

表单收集到的日期数据结构是:{ "date": ["2023-04-01","2023-04-23"] },而服务端要求的数据结构是:{ "startDate": "2023-04-01", "endDate": "2023-04-23" }

图片

 const schema = {


   "type": "object",


   "properties": {



     "date": {
       "bind": [
         "startDate",
         "endDate"
       ],
       "title": "日期",
       "type": "range",
       "format": "date",
       "description": "bind 转换"
     },
     "date1": {
       "title": "日期",
       "type": "range",
       "format": "date",
       "description": "未进行转换"
     }
   }
 }

场景二

相信用过 FormRender 1.0 版的用户应该都清楚,使用 List 组件收集到的数据结构是:{ "list": [{ "size": 1 }, {"size": 2}]},而你希望的数据结构可能是 { "list": [1, 2] }, 2.0 已经完美支持。

图片

 const schema = {


   "type": "object",


   "properties": {



     "list": {
       "type": "array",
       "widget": "simpleList",
       "items": {
         "type": "object",
         "column": 3,
         "properties": {
           "input": {
             "bind": "root",
             "title": "大小",
             "type": "number"
           }
         }
       }
     }
   }
 }

更多关于 bind 字段的使用技巧,请前往官方文档进行阅读

4、组件自定义

FormRender 提供了一些基础组件,例如 input、select 和 radio 等,但有时候这些组件并不能完全符合我们的业务需求,此时可以考虑使用自定义组件。FormRender 提供了丰富的自定义组件 API 和接口,以满足业务在表单组件上的个性化需求。

除此之外,FormRender 还支持自定义嵌套组件(object)和列表组件(list),所以真正意义上的自定义组件包括三种类型:输入控件、嵌套组件和列表组件。在使用自定义组件时,可以根据不同的类型使用相应的自定义 API 和接口对组件进行个性化调整和扩展。

输入控件自定义

在使用自定义输入控件时,只需要让该控件能够接收 value 和 onChange 两个基本属性即可。这两个属性是表单项数据和改变数据的事件处理函数,是实现表单控件与表单数据之间双向绑定的必要条件。因此,只要自定义组件能够接收这两个属性,就可以与 FormRender 进行良好的配合。

图片

 const CaptchaInput = (props: any) => {
   const { value, onChange, addons } = props;




   const sendCaptcha = (phone: string) => {
     console.log('send captcha to:', phone);
   }

   return (
     <Space>
       <Input
         value={value}
         onChange={(e) => onChange(e.target.value)}
         placeholder="请输入手机号"
       />
       <Button onClick={() => sendCaptcha(value)}>发送验证码</Button>
     </Space>
   );
 };
 import React from 'react';

 import { Input, Button, Space } from 'antd';
 import Form, { useForm } from 'form-render';
 import CaptchaInput from './CaptchaInput';


 const schema = {
   type: 'object',
   properties: {
     phone: {
       title: '自定义 Input',
       type: 'string',
       widget: 'CaptchaInput',
       props: {}
     }
   }
 };

 const Demo = () => {
   const form = useForm();


   return (
     <Form
       form={form}
       schema={schema}
       widgets={{ CaptchaInput }}
     />
   );
 };

 export default Demo;

嵌套组件自定义

在进行嵌套组件的自定义时,需要重点关注 children 属性。children 是嵌套组件的内部表单项,可以直接渲染而无需过多关注。FormRender 会将嵌套组件的 schema 字段透传到嵌套组件的 props 中,并将其打散以渲染对应的子表单项。因此,只要自定义组件能够正确接收 props 中的数据并渲染 children 中的表单项即可。

图片

 import React from 'react';

 import { Card } from 'antd';




 import './index.less';


 const CustomCard = ({ children, title, description }) => {
   if (!title) {
     return children;
   }

   return (
     <Card
       title={
         <>
           {title}
           {description && (
             <span className='fr-header-desc'>
               {description}
             </span>
           )}
         </>
       }
     >
       {children}
     </Card>
   );
 }


 export default CustomCard;
 import React from 'react';
 import FormRender, { useForm } from 'form-render';
 import CustomCard from './CustomCard';



 const schema = {
   type: 'object',
   displayType: 'row',
   properties: {
     obj: {
       type: 'object',
       widget: 'CustomCard',
       title: '卡片主题',
       description: '这是一个对象类型',
       column: 3,
       properties: {
         input1: {
           title: '输入框 A',
           type: 'string'
         },
         input2: {
           title: '输入框 B',
           type: 'string'
         },
         input3: {
           title: '输入框 C',
           type: 'string'
         },
         input4: {
           title: '输入框 D',
           type: 'string'
         }
       }
     }
   }
 };

 export default () => {
   const form = useForm();

   return <FormRender schema={schema} form={form} widgets={{ CustomCard }/>;
 };

列表组件自定义

列表组件的实现稍微复杂一点,一般都会提供扩展参数来实现,但如果过于繁琐的话,可以参照源码对应的 list 组件进行自定义。

图片

 import React, { useState, useContext } from 'react';
 import { Tabs } from 'antd';
 import './index.less';



 const TabPane = Tabs.TabPane;


 const TabList = (props) => {
   const {
     schema,
     fields,
     rootPath,
     renderCore,
     readOnly,
     delConfirmProps,
     tabName,
     hideDelete,
     hideAdd,
     addItem,
     removeItem,
     tabItemProps = {}
   } = props;

   return (
       <Tabs>
         {fields.map(({ key, name }) => {
           return (
             <TabPane>
               <div style={{ flex: 1 }}>
                 {renderCore({ schema, parentPath: [name], rootPath: [...rootPath, name] })}
               </div>
             </TabPane>
           );
         })}
       </Tabs>
   );
 }

 export default TabList;

占位组件 (试验)

纯展示型组件,独占一行,可以用于表单模块分割使用,其他场景待开发

 void2: {
     title: '其他组件',
     type: 'void', // 关键配置
     widget: 'voidTitle'
 }

图片

写在最后

作为一款开箱即用的表单解决方案,FormRender 可以大幅提高中后台系统中的表单开发效率和灵活性,让你可以快速创建各种类型的表单,并省略从头编写表单组件的繁琐步骤。我们将一直坚持这个初衷,并不断推进协议配置方面的创新和提升,努力提供更加完善的表单开发体验。

接下来我们将推出适配 2.0 协议的表单设计器,该设计器将支持自定义二次开发功能,并支持在移动端场景下拖拽生成 schema 协议。这将极大地提高表单设计和开发的便捷性。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYiEbQI4' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片