组件联动是指几个组件之间相互关联。也就是说,当一个组件的状态发生变化时,其他组件可以响应。组件联动是多对多关系,目的分为一次性和连续:多对多关系:即一个组件可以同时被多个组件联动;多个组件可以同时链接到一个组件。一次性持久化:一次性事件可以被覆盖,持久化事件会同时生效,需要考虑叠加关系。持久化事件在一定程度上可以覆盖一次性事件场景:组件可以一直响应最后一次到来的事件。接下来我们引入组件值和值联动两个概念来实现持续联动功能。组件值每个组件实例都有一个唯一的组件值。我们可以通过getValue(componentId)和setValue(componentId,value)访问或更新组件值:consttable={componentName:"table",runtimeProps:({componentId,setValue})=>({//注入onChange函数给组件,在onChange触发时更新当前组件实例的组件值:(value)=>setValue(componentId,value),}),};也可以通过componentMeta.value声明组件值,比如下面的例子,让组件值和props.value同步:consttable={componentName:"table",//声明value作为组件props的返回值。值,并在更新组件props.value时更新值:({selector})=>selector(({props})=>props.value),};以上两种方式任选一种使用。为什么一个组件实例只有一个组件值?一个组件可能同时有多个状态。比如组件内部有一个输入框和一个按钮。输入框的值可能会与其他组件上按钮的点击状态产生联动效果。但这并不意味着一个组件实例需要多个组件值,我们可以将组件值定义为一个对象,合理规划不同的key来描述不同维度的值://组件值结构{//的值组件中的输入框text:'123',//组件中按钮被按下的次数buttonClickTimes:false}为什么不使用props.value而不是组件值呢?理论上是可以的,但这限制了组件的props定义。可能有些组件使用props.value来描述输入框的值,但也有组件比如Check使用props.checked来表示当前选中状态。只有抽象出一个定义规则和组件元信息,让业务自由连接,组件值才能适配任何类型的组件。值联动有了组件值的概念,就可以在组件实例的粒度上设计组件之间的关系。为了让组件关联更加灵活,我们的设计需要满足以下能力:联动关系支持多对多。随着全局数据状态的变化或组件自身props的变化,组件关联关系可以随时改变。一个组件可以定义其他几个组件的关联关系,即使它不参与联动关系链。当一个组件实例被删除时,它所定义的链接关系立即失效。估计我们是用componentMeta.valueRelates声明式定义值链接关系:consttable={componentName:"table",valueRelates:({componentId,selector})=>{return[{sourceComponentId:componentId,//本身就是触发源targetComponentId:selector(({props})=>props.targetComponentId),//目标组件ID为props.targetComponentId},];},};这种设计可以同时满足以上四个需求,解释如下:可以在任意组件实例中定义多个联动关系,自然可以实现多对多联动。valueRelates引入了一个selector,可以响应state或者props的变化,可以被任意state驱动更新联动关系。如果source和target都不指向自己,则不参与联动关系链。当组件实例被销毁时,声明式定义方法自然会失败。那么组件如何响应联动呢?重点就在这里,组件可以通过selector(({relates})=>)的relates获取自己当前的链接状态,例如:consttable={componentName:"table",runtimeProps:({selector})=>{//关系结构如下,可以获取作用于自身的各个组件的实例ID和最新值//[{//sourceComponentId:'abc',//value:'123'//}]constrelates=选择器(({相关})=>相关);返回{状态:relates.length>0?“链接”:“免费”,};},};如果我们使用selector来监听runtimeProps中的related,我们可以在link状态发生变化时,驱动组件渲染并传入linkage的相关状态;如果在fetcher中使用selector监控relates,可以在linkage状态改变时触发query,等等。未来我们会扩展越来越多的组件元数据回调函数。支持选择器后,可以声明式地响应relates的变化,即组件可以声明式、灵活地响应linkage。真正意义上,任何场景都可以使用联动。框架没有为联动做太多的联动内置行为,实现了灵活的规则。虽然业务需要完成很多报表,但优势在于灵活性和统一使用。描述链接行为。不同的联系可能会做不同的事情。例如,一个输入框组件可能同时具有以下两个功能:使另一个组件查询条件增加“wherename=”当前输入框的值。当组件的值为“删除”时,使画布的另一个组件隐藏。为了区分联动函数,可以在valueRelates中添加一个payload参数来描述联动的目的:componentId,targetComponentId:selector(({props})=>props.targetComponentId),//作为目标组件的查询过滤条件payload:"filter",},{sourceComponentId:componentId,targetComponentId:selector(({props})=>props.targetComponentId),//目标组件是否隐藏payload:"hide",},];},};然后target组件可以根据实际情况在fetcher中过滤relates中payload="filter"的值,在runtimeProps中过滤relates中payload="hide"的值。使用连续联动实现一次性联动。组件每次更新值时,都会刷新目标组件关联的位置。具体来说,会放在最上面,这样就可以根据relates的先来后到的顺序来判断目标组件。比如当联动效果发生冲突时,让第一个优先生效。例如:consttable={componentName:"table",runtimeProps:({selector})=>{//找到最开始生效的链接,payload为color,覆盖props.colorconstrelateColor=selector(({relates})=>relates.find((each)=>each.payload==="color"));返回{颜色:relateColor,};},};当另一个组件触发值变化时,它会排在目标组件relatedFirst,这样,如果目标组件如上写响应代码,它会一直响应最后一个有效的链接。小结本节介绍如何建立联动,并介绍组件值的概念。在框架层定义抽象的组件值概念,通过声明式或调用式对接的方式连接state状态或组件props。这个抽象的概念会贯穿整个框架设计过程。类似valueRelates也有声明的能力,通过selector的relates对象将联动功能传递给组件实例,大大增加了联动消费的灵活性。可视化构建框架的设计思路可能大同小异,但遗憾的是很多构建框架都对联动、查询等场景进行了自定义约束,使得每个框架或多或少都有私有协议,而我认为在这个系列中是什么强调的是可以进一步抽象,让框架为业务提供自由定义协议的能力,而不是提供固定的协议。讨论地址为:Jingdu《组件值与联动》·Issue#469·dt-fe/weekly想参与讨论的请点这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号
