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

可视化构建-定义联动协议

时间:2023-03-27 14:19:10 JavaScript

虽然底层框架提供了通用的组件值和联动配置,可以建立到组件任意props的映射,但这只是能力,不是协议。业务层可以确定协议,并使协议具有可扩展性。我们先从用户的角度来设计API,再看看如何基于已有的组件值和联动能力来实现。设计联动协议首先,不同的业务方会定义不同的联动协议,因此需要通过扩展的方式注入联动协议:={onReadComponentMeta}/>首先,可视化构建框架支持onReadComponentMeta属性,用于扩展所有注册组件的元信息,联动协议的扩展基于组件值和组件联动能力,所以这是最合理的扩容方式。之后,我们注册了一个固定的联动协议,如下:{"componentName":"input","linkage":[{"target":"input1","do":{"value":"{{$self.value+'hello'}}"}}]}只要在组件实例上定义联动属性,联动就可以生效。比如上面的例子:target:联动目标。做:联动效应。比如本例中,组件ID为input1,组件值同步为当前组件实例的组件值+'hello'。$self:描述你自己的实例,比如你可以从$self.value中得到你自己的组件值,从$self.props中得到你自己的props。更进一步,target还可以支持数组,这意味着同一规则同时适用于多个组件。我们还可以支持更复杂的语法,比如允许这个组件同步其他组件的值:{"componentName":"input","linkage":[{"deps":["input1","input2"]"props":{"text":"{{$deps[0].value+deps[1].value}}"}}]}上面的例子是说组件实例的props.text同步为input1的组件值+input2:deps:描述依赖列表,在表达式中使用$deps[]可以访问每个依赖实例,例如$deps[0].props可以访问组件ID为input1的组件的props。props:同步组件的props。如果定义了target,则作用于target组件,如果没有定义target,则作用于自身。但无论如何,表达式的$self指向它自己的实例。总结一下,这个联动协议可以让组件实例实现如下效果:设置组件值和组件道具的联动效果。可以将自己的组件值同步到组件实例,也可以将其他组件值同步到自己。基本上,它可以满足将任何组件链接到任何组件的需求。它甚至支持组件之间的传输。比如组件A的组件值同步到组件B,组件B的组件值同步到组件C,组件A的setValue()后,组件B和组件C的组件值都会更新同时。联动协议的实现上面的联动协议只是一个实现。我们可以根据组件值和组件联动设置任何协议,所以实现联动协议的思路具有普适性,但为了方便起见,我们还是以上述协议为例来说明如何使用实现协议的可视化框架。首先解释组件实例的联动属性,将联动定义转化为组件联动关系,因为联动协议本质上是产生组件联动。接下来的代码片段比较长,我会尽量使用代码注释来解释:constextendMeta={//定义valueRelates关系,也就是我们在上一节中提到的定义组件联动关系的关键valueRelates:({componentId,selector})=>{//使用选择器读取组件实例的联动属性//由于选择器的特性,会实时更新,所以联动协议改变后,联动状态也会实时更新constlinkage=selector(({componentInstance})=>componentInstance.linkage)//返回联动数组,结构:[{sourceComponentId,targetComponentId,payload}]returnlinkage.map(relation=>{constresult=[];//定义这种类型的链接,称为simpleRelationconstpayload={type:'simpleRelation',do:JSON.parse(JSON.stringify(relation.do)//将$deps[index]替换为$deps[componentId].replace(/\$deps\[([0-9]+)\]/g,(匹配:string,index:string)=>`$deps['${relation.deps[Number(index)]}']`,)//用$deps[componentId]替换$self.replace(/\$self/g,()=>`$deps['${组件Id}']`),),};//上面代码之后,是否是$self。或$deps[0]。表达式中转换成具体的组件ID//$deps[componentId],这样后面的处理流程就简单统一//读取deps,定义dep组件为源,target为目标组件//这是最关键的一步,将dep->target关系绑定到relation.target.forEach((targetComponentId)=>{if(relation.deps){relation.deps.forEach((depIdPath:string)=>{result.push({sourceComponentId:depIdPath,targetComponentId,});});}//定义自己到目标组件的联动关系result.push({sourceComponentId:componentId,targetComponentId,payload,});});返回结果;}).flat()}}上述代码使用valueRelates提取联动协议的关联关系,转化为值联动关系接下来,我们需要实现道具同步功能。要实现这个功能,我们自然要使用runtimeProps和selector.relates,根据联动协议的表达式执行当前组件关联的组件值,并更新为对应的key。下面是大致的实现思路:constextendMeta={runtimeProps:({componentId,selector,getProps,getMergedProps})=>{//获取作用于自身的值关联信息:relatesconstrelates=selector(({relates})=>相关);//记录最终受值联动影响的propsletrelationProps:any={};//记录此时自己组件关联的组件值const$deps=relates?.reduce((result,next)=>({...result,[next.componentId]:{value:next.value,},}),{},);//为使每次依赖变化生效,多对一do的每一项都带过来,需要先根据relationIndex去重relates.filter((relate)=>relate.payload?.type==='simpleRelation').forEach((relate)=>{constexpressionArgs={//$deps[].value指向依赖值$deps,get,getProps:relate.componentId===componentId?getProps:getMergedProps,};//处理props链接if(isObject(relate.payload?.do?.props)){Object.keys(relate.payload?.do?.props).forEach((propsKey)=>{relationProps=set(propsKey,selector(()=>//这个函数是key,传入组件props和expression,返回新的props值ofexpressions不同,所以不启用缓存cache:false,},),relationProps,);});}});returnrelationProps}}比较复杂的函数是getExpressionResult,需要解析表达式并执行。原理是利用代码沙箱执行字符串函数,将变量名替换为正则表达式,匹配上下文中的变量。大致代码如下://代码执行沙箱,传入stringjs函数,使用newFunction执行函数sandBox(code:string){//with是关键,使用with自定义代码执行的上下文constwithStr=`with(obj){${code}}`;constfun=newFunction('obj',withStr);返回rnfunction(obj:any){returnfun(obj);};}//获取沙箱代码执行结果,可以传入参数覆盖沙箱上下文函数getSandBoxReturnValue(code:string,args={}){}catch(error){//eslint-disable-next-lineno-consoleconsole.warn(error);}}//如果对象是字符串,直接返回,即{{}}表达式执行并返回functiongetExpressionResult(code:string,args={}){if(code.startsWith('{{')&&code.endsWith('}}')){//{{}}里面的表达式letcodeContent=code.slice(2,code.length-2);//将$deps['id'].props.a.b.c这样的形式//转换为get('a.b.c',getProps('id'))codeContent=codeContent.replace(/\$deps\[['"]([a-zA-Z0-9]*)['"]\]\.props\.([a-zA-Z0-9.]*)/g,(str:string,componentId:string,propsKeyPath:string)=>{return`get('${propsKeyPath}',getProps('${componentId}'))`;},);返回getSandBoxReturnValue(`return${codeContent}`,args);}returncode;}其中with是沙盒执行时替换代码上下文的关键总结componentMeta.valueRelates和componentMeta.runtimeProps可以灵活定义组件联动关系,更新组件props。使用这两个声明式API,您甚至可以实现组件链接协议。总结起来,它包括以下几个关键点:使用valueRelates将deps和target转化为组件值关联。将联动协议定义的相对关系(易写易记)转化为绝对关系(使用componentId定位),方便框架处理。使用with执行表达式上下文。使用runtimeProps+selector实现注入组件props的变化与响应联动值关联,从而实现按需联动。讨论地址为:Jingdu《定义联动协议》·Issue#471·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)