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

组件注册和画布渲染

时间:2023-03-27 13:45:55 JavaScript

继视觉构建的理论抽象之后,我们开始勾勒出一个具体的React视觉构建器。精读如果我们将整体的视觉构造定义为,那么API可能是这样的:componentTree:定义组件树结构。只要注册组件元信息和组件树,就可以渲染出可视化构建的画布,简单易懂。我们先来看看组件树是如何定义的:组件树中有每个组件的实例,所以最好的设计是组件树和组件实例结构是同构的,叫做ComponentInstance-componentinstance:{"componentName":"container","children":[{"componentName":"text","props":{"name":"我是一个文本组件"}}]}以上结构可以作为一个组件的实例信息单组件,也可以认为是组件树,即组件树的任意一个组件节点都可以取出来成为新的组件树,这就是同构的意思。我们定义了最基本的组件树结构,以后所有的功能都将基于这三个元素展开:componentName:组件名称,描述组件类型,如文本、图片或表格。props:组件实例的所有配置信息透明传递给组件props。children:子组件,类型为ComponentInstance[]。每一个概念都是不可或缺的,我们从概念的必要性来分析一下这三个属性:componentName:必须有属性,否则怎么渲染节点都不可能。因此,相应地,我们需要组件元信息来定义每个组件名称应该如何呈现。props:即使是相同组件名称的不同实例也可能有不同的配置。将这些配置放在props中就可以了,不需要再开额外的属性来存放各种业务配置。children:理论上可以合并到props.children中,但是由于子组件这个概念太普通了,建议同时支持children和props.children。同时定义时,前者优先级高。此外,还有一个可选属性componentId,它是组件的唯一ID。我们从可选性和必要性的角度来分析一下这个属性:componentId的可选性:组件实例在组件树中的路径是自然组件的唯一ID。比如上面的文本组件的组件的唯一ID就可以认为是children。0、componentId的必要性:使用组件树路径代替组件唯一ID的缺点是组件在组件树上移动后,组件的唯一性会消失。这时候就必须要用到componentId了。视觉脚手架的一个很好的实现是支持可选的componentId。组件元信息遵循上述,至少要定义一个组件名称如何渲染,所以组件元信息(ComponentMeta)的必要结构如下:consttextMeta={componentName:"text",element:({name})=>{name},};componentName:定义组件名称的元信息。元素:此组件的渲染函数。实现了这些最基本的功能后,可视化构建器虽然没有任何实质性的功能,但至少完成了一个核心的基础工作:组件树结构的描述与实现分离。即使以后不增加任何功能,也会永久改变开发模式。我们需要先定义组件元信息,然后将其放置在组件树上。对于画板工具软件,如果不考虑布局等复杂的画布功能,这种结构描述足以完成大部分技术抽象:配置面板修改组件实例的props属性,甚至布局位置也可以存储在道具上。对于元素的命名,可能存在差异。比如还有render、renderer、reactNode等其他的命名方式,但是不管叫什么名字,只要是基于React响应式定义的,都应该以同样的方式结束。顶多对于各种Key名称的定义不同,这一块可以保持自己的观点。我们继续关注组件元信息的元素属性,看下面的元素代码:constdivMeta={componentName:"div",element:({children,header})=>(}

),};在上面的例子中,我们可以识别孩子和标题类型吗?可以识别一个部分:children:必须是一个React实例,可以是一个或多个组件实例。header:可以是数字、字符串或React实例。props.children对应componentInstance.children的描述,那么如何识别header是普通对象还是React实例呢?Props上的ComponentTreeLike属性ComponentTreeLike指的是:在组件props属性上,识别“类似组件实例的属性”,并将其转换为真正的组件实例,传递给组件。假设一个普通的props.header值为"sometext",那么组件props实际获取的props.header值也是字符串"sometext":{"componentName":"div","props":{"header":"sometext"}}constdivMeta={componentName:"div",element:({header})=>(
{header}{/**string"sometext"*/}
),};如果将props.header写成类子结构,可视化构建框架会将其识别为组件实例,将其转换为真正的React实例,然后将其传递给组件:{"componentName":"div","props":{"header":[{"componentName":"text"}]}}constdivMeta={componentName:"div",element:({header})=>(
{header}{/**React组件实例,此时会渲染组件实例*/}
),};这种设计基于一个原则:组件树应该能够描述任何组件想要的props属性。我们从元素的角度来看。假设你注入了一个框架组件,比如Antd。如果你想在不改变一行源代码的情况下接入平台,平台必须满足配置任何道具的能力。除了基本的变量,还有更复杂的React组件实例和函数。现在我们已经解决了传递组件实例的问题。至于如何传递函数,我们下一节再说。这种设计有两个缺陷:由于ComponentTreeLike会自动转化为一个实例,所以组件无法获取到ComponentTreeLike的原始值。由于ComponentTreeLike的位置不确定,为了避免深度解析带来的性能损失,只解析props的一级节点,会导致嵌套层次较深的ComponentTreeLike无法解析。如果要解决这两个缺陷,需要在组件元信息上定义Props的类型,例如:constdivMeta={componentName:"div",propsType:{header:"element",content:["element"],标签:[{面板:"元素",},],},};解释上面的例子是什么意思:header:是一个单一的ReactElement。内容:是一个React元素数组。tabs:是一个数组结构,每一项都是一个对象,panel是一个ReactElement。这样,通过下面的组件树的描述,就可以将对应的元素类型准确的转换为组件实例,将基本类型原语原样传递给组件:{"componentName":"div",“道具”:{“标题”:{“组件名称”:“文本”},“名称”:[“a”,“b”,“c”],“内容”:[{“组件名称”:“文本”},{"componentName":"text"}],"tabs":[{"title":"tab1","panel":{"componentName":"text"}}]}}这样,属性未定义为Element不会作为React实例处理,第一个问题自然就解决了。通过配置更深层次的嵌套结构,第二个问题自然也就解决了。componentMeta.propsType之所以没有使用JSONSchema结构,是因为框架不需要内置验证props类型的能力。这种能力可以交给业务层处理,所以这里可以采用简化的版本结构,方便编写,也便于阅读。注意:propsType中的{}表示value是一个对象,[]表示value是一个数组。为数组时,只支持单个子元素,因为单个项是定义数组中每一项的类型。给组件注入函数,现在可以将React实例的任何基本类型和props传入componentMeta.element,但是仍然缺少函数类型或者Set、Map等复杂类型需要解决。由于组件树结构需要序列化到库中,所以必须是可序列化的JSON结构,而这个结构需要暴露给开发者,所以不适合定义一些hack的序列化反序列化规则。因此,要将函数注入到组件props中,需要在组件元信息上进行定义。由于它定义了额外的props属性并且不在组件树中,我们将其命名为runtimeProps:constdivMeta={componentName:"div",runtimeProps:()=>({onClick:()=>{console.log('click')}})元素:({onClick})=>(点击我),};点击按钮后,click会被打印出来。这是因为runtimeProps定义了函数类型onClick以在运行时传入组件props。当组件树和componentMeta.runtimeProps同时定义相同的key时,runtimeProps具有更高的优先级。小结本节我们已经介绍了组件注册和画布渲染的基本内容,我们再来回顾一下。首先定义了API,支持componentTree和componentMetas。有了组件树和组件元信息,就可以实现可视化构造画布的渲染。我们还介绍了如何在组件元信息中定义组件的渲染函数,如何将基础变量、React实例和函数传入渲染函数props,使得渲染函数无需任何适配就可以对接任何成熟的组件库组件库工作。但这只是视觉构建的第一步。当你真正开始项目之后,你会遇到越来越多的问题。例如,除了渲染画布之外,还需要定义属性配置面板、组件拖动列表、图形图层列表、撤销重做等功能,这些功能如何获取画布属性呢?如何与画布交互?runtimeProps是如何根据项目数据流向组件注入不同的属性或功能的?如何根据组件props的变化动态注入不同的功能?如何保证注入的函数引用不变?要解决这些问题,需要在本章的基础上实现一套系统的数据流规则和配套的API,这也是下一讲的内容。讨论地址为:Jingdu《组件注册与画布渲染》·Issue#464·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)