当前位置: 首页 > Web前端 > JavaScript

JSONSchema&FormUI的快速生成和分析

时间:2023-03-27 00:27:20 JavaScript

1.JSONSchemaJSON(JavaScriptObjectNotation)是一种轻量级的通用数据交换格式。基本数据结构为key-value,优点是易于生成和解析,程序需要的数据结构可以通过JSON灵活表达。但是JSON本身并没有具体的规范(其结构不支持评论),因此缺少对数据本身的描述。例如,开发者或程序无法判断后面数据中的年龄是否为字符串。预期的类型。{"name":"李四","mobile":"1370000001","age":"30"}JSONSchema定义了一套能够更完整描述JSON的规范,基于JSONSchema的规范数据我们需要的结构,或者根据这个规范开发程序,都可以达到预期的效果。使用场景:1.数据校验可能是JSONSchema最常见的场景,无论是前端还是后端,都需要校验数据,表单校验,CI/CD自动化测试,以及很快。以上面的JSON为例,如果要指定年龄为数字,必须小于等于20,可以声明一个JSONSchema{"$schema":"http://json-schema.org/schema","title":"Person","description":"anexample","type":"object","properties":{"name":{"type":"string"},"mobile":{"type":"string"},"age":{"type":"number","maximum":20},}}那么当age是字符串或者数字,但是不在range,会提示验证失败简单验证示例:https://www.jsonschemavalidat...ApplicationAjv.js基于JSONSchema的验证库,常用于nodejs、浏览器、微信小程序和其他场景,通过声明一个JSONSchema,无需代码开发即可快速验证数据。示例:constAjv=require('ajv');constajv=newAjv();//模式constschema={$schema:'http://json-schema.org/schema',...};constvalidate=ajv.compile(schema);//验证数据constvalidData={...age:'30',};constvalidResult=validate(validData);if(validResult){//验证通过console.log('pass');}else{//验证失败console.log(validate.errors);//[//{//keyword:'type',//dataPath:'.age',//schemaPath:'#/properties/age/type',//params:{type:'number'},//message:'shouldbenumber'//}//]}不仅是JavaScript/Typescript,其他编程语言也是基于JSONSchema实现的验证器,比如Java的Snow,go的gojsonschema,Python的jschon,都是以此为基础开发的。因此,通过JSONSchema规范,也可以保持前后端验证的一致性。2.表单自动生成JSONSchema虽然有规范约束,但它仍然是一种描述数据的JSON配置。基于这样的配置,从逻辑上讲,可以自动渲染出一个功能完备的表单UI。应用vue-json-schema-form:基于vue.js和JSONSchema渲染表单,最新版本已经支持Vue3form-render:基于react.js的表单解决方案,最新版本使用AntDesign作为视觉主题formily:cross-endcapability,逻辑可以跨框架,主模块(以react.js+antd为例):@formily/core:实现状态管理,表单验证等逻辑,与UI无关@formily/react:实现交互效果,视图桥接@formily/antd:扩展组件库,开箱即用的表单UI以上所有的表单渲染库都提供了对应的表单设计器,可以通过拖拽的方式快速生成JSONSchema。整体流程如下:如何选择合适的库?如果是基于react.js的低代码项目,那么form-render其实就够用了。formily虽然支持很多场景,但是有一定的接入成本(从官方文档可以看出),而且包体积比较大。比如form-render,只需要引入form-render,然后正确传入props,就可以直接渲染出预期的UI。一般在低代码平台,都会有form模块,但这部分逻辑是通常不是整个低代码项目的核心,所以可以交给form-render之类的表单渲染库,以此为基础,可以减少单独维护表单映射或验证的开发代码。当然,也可能需要开发一些定制化的widget,以适应更复杂或更利于业务的情况。2.form-renderfrom-render作为一个整体可以分为core和widgets。core实现表单映射、验证和监控等,widgets由一些UI组件实现。核心地图微件包括内置组件和扩展组件。内置组件已经提供了,基本都包含在这里:https://x-render.gitee.io/gen...。同时也支持开发者自定义的一些扩展组件。提供props.widgets传入自定义对象,将扩展表单组件注册到widgets映射表中。//form-render-core/src/index.js如果要覆盖默认组件,可以使用映射在表单映射表中注册//form-render-core/src/index.jsconsttools=useMemo(()=>({widgets,mapping:{...defaultMapping,...mapping},...需要注意的是,这只是表单映射表,你还需要将自定义的widgets注册到表中。无论是内置或者扩展的组件,只要实现了一个基于映射表的getWidgetName方法,就可以获取到需要映射的组件名称,并渲染相应的UI。//form-render-core/src/core/RenderField/ExtendedWidget.js//JSONSchema指定widgetletwidgetName=getWidgetName(schema,mapping);constcustomName=schema.widget||模式['ui:widget'];if(customName&&widgets[customName]){widgetName=customName;}constreadOnlyName=schema.readOnlyWidget||'html';//指定widget为readOnly模式,或者使用默认的html(!widgetName){widgetName='输入';返回;}constWidget=widgets[widgetName];constextraSchema=extraSchemaList[widgetName];...//form-render-core/src/core/RenderField/index.js//单属性UIconstRenderField=props=>{...return(<>{_showTitle&&titleElement}{/*Widget渲染*/}{/*解释信息*/}{/*ErrorMessage,验证相关*/}

);}Validation需要实现两个基本的验证方法,validateSingle(单属性验证)和validateAll(表单验证),具体的验证逻辑可以通过一些开源实现工具。例如,form-render使用async-validator作为验证工具。Async-validator是一个异步验证表单的工具。Ajv.js也可以异步验证。在上面的模式中引入{$async:true}。ajv.jsasync-validatorserver支持客户端,支持同步验证,不支持异步验证,支持包大小119.6kb14.2kb大多数情况下,表单验证会选择异步执行,所以表单渲染库比如form-render,或者一些开源组件库(比如element)会使用async-validator作为验证工具。//form-render-core/src/core/RenderField/index.jsconstvalidateSingle=(data,schema={},path,options={})=>{.../***getDescriptorSimple将被转换为匹配async-validator的数据结构,如果是其他验证工具,可能是另一种转换*path是key,rules是value,[path]:result的data是对应的*/constdescriptor=getDescriptorSimple(模式,路径);让验证者;try{//验证validator=newValidator(descriptor);}catch(error){返回Promise.resolve();}//错误消息模板类型编号字符串letmessageFeed=locale==='en'?zh:cn;合并(消息提要,验证消息);validator.messages(messageFeed);return验证器.validate({[path]:data}).then(res=>{return[{field:path,message:null}];}).catch(({errors,fields})=>{//returnerrors;});};validateAll只需要根据validateSingle遍历完成校验即可。除了作为validateAll的一部分,validateSingle还将在validateField中用于对单个属性进行实时验证。constonChange=value=>{//节流、表单方法等...validateField({path:dataPath,//pathformData:formDataRef.current,//表单数据扁平化,//模式转换结构,[path]:{parent,children,schema}options:{locale,validateMessages,},})...};仅仅有验证是不够的,最重要的是提示数据验证失败的原因,所以也是需要实现消息动态模板,ErrorMessage组件携带错误提示。比如form-render实现validateMessageCN.js作为消息模板,ErrorMessage.js作为错误提示组件。监控数据监控在低代码场景中很常见。希望在用户输入相应的属性后,渲染器能够实时响应,同步渲染UI。form-render提供了watch属性,用于调用数据监控的回调。//form-render-core/src/Watcher.js.../***formData当前表单的数据,watchKey监听的key*getValueByPath主要处理#和普通key*如果是#,则返回是formData*/constvalue=getValueByPath(formData,watchKey);//回调constwatchObj=watch[watchKey];useEffect(()=>{construnWatcher=()=>{if(typeofwatchObj==='function'){try{//执行回调函数并将值传递给外层watchObj(value);}catch(error){console.log(执行`${watchKey}对应的watch函数报错:`,error);}}elseif(watchObj&&typeofwatchObj.handler==='function'){try{//适配多参数,其实目前主要是handler和immediatewatchObj.handler(value);}catch(error){console.log(`${watchKey}对应的watch函数报错:`,error);}}};if(firstMount){constimmediate=watchObj&&watchObj.immediate;if(immediate){//如果immediate为true,将在第一次加载时触发watchrunWatcher();}}else{runWatcher();}...需要注意的是,在嵌套对象或者数组的情况下,getValueByPath还需要具备根据路径获取值的能力,比如form-render通过lodash-es模块方法的get来实现。通过watch映射表构造多个watch实例。...{{/*watchList=Object.keys(watch)*/}watchList.length>0?watchList.map((item,idx)=>{{/*null*/}return();}):null}...widgetsWidgets主要包含内置组件,有些组件直接使用了组件库提供的组件,比如TextArea,InputNumber等,这些组件可以直接仅通过调整样式用于表单渲染;但是大部分组件都是封装后使用的,比如Slider、Color和Date组件等,不同的组件封装了不同的逻辑。例如Slider包含组件库的Slider和InputNumber,解构schema构建相应的props。form-render自定义组件:input,checkbox,checkboxes,color,date,time,dateRange,timeRange,imageInput,url,list,map,multiSelect,radio,select,slider,switch,upload,html,rateform-render组件库组件:number、textarea、treeSelect