接着可视化搭建的理论抽象,我们开始勾勒一个具体的 react 可视化搭建器。
假如我们将可视化搭建整体定义为 <Designer>,那么 api 可能是这样的:
<Designer componentMetas={[]} componentTree={} />
只要注册了组件元信息与组件树,可视化搭建的画布就可以渲染出来了,这很好理解。
我们先看组件树如何定义:
组件树里有各组件的实例,那么最好的设计是,组件树与组件实例结构是同构的,称为 ComponentInstance - 组件实例:
{
"componentName": "container",
"children": [
{
"componentName": "text",
"props": {
"name": "我是一个文本组件"
}
}
]
}
上面的结构既可以当做单个组件的 组件实例信息,也可以认为是一个 组件树,也就是组件树的任何组件节点都可以拎出来成为一个新组件树,这就是同构的含义。
我们定义了最最基础的组件树结构,以后所有功能都基于这三个要素来拓展:
每一个概念都不可或缺,让我们从概念必要性再分析一下这三个属性:
除此之外,还有一个可选属性 componentId,即组件唯一 ID。我们从可选性与必要性两个角度分析一下这个属性:
一个好的可视化搭建实现是支持 componentId 的可选性。
接着上面说的,至少要定义一个组件名是如何渲染的,所以组件元信息(ComponentMeta)的必要结构如下:
const textMeta = {
componentName: "text",
element: ({ name }) => <span>{name}</span>,
};
实现这些最基础功能后,虽然该可视化搭建器没有人任何实质性的功能,但至少完成了一个核心基础工作:将组件树结构的描述与实现分开了。哪怕以后什么功能也不再增加,也永久的改变了开发模式,我们需要先定义组件元信息,再将其放置在组件树上。
对于画板工具软件,如果不考虑布局等复杂的画布功能,该结构描述足以完成大部分工作的技术抽象:配置面板修改组件实例的 props 属性,甚至布局位置也可以存储在 props 上。
对于 element 的命名,可能会产生分歧,比如还有其他命名风格如 render、renderer、reactNode 等等,但不管叫什么名字,只要是基于 React 响应式定义的,最终应该都殊途同归,最多对于各类 Key 的名称定义有所不同,这块可以保留自己的观点。
我们继续聚焦组件元信息的 element 属性,看以下 element 代码:
const divMeta = {
componentName: "div",
element: ({ children, header }) => (
<div>
{children}
{header}
</div>
),
};
上面的例子中,我们可以识别出 children 与 header 类型吗?可以识别一部分:
props.children 对应了 componentInstance.children 描述,那么如何识别 header 是一个普通对象还是 React 实例呢?
ComponentTreeLike 指的是:组件 props 属性上,识别出 “像组件实例的属性”,并将其转换为真正的组件实例传给组件。
假设一个正常的 props.header 值为 "some text",那么组件 props 实际拿到的 props.header 值也是字符串 "some text":
{
"componentName": "div",
"props": {
"header": "some text"
}
}
const divMeta = {
componentName: "div",
element: ({ header }) => (
<div>
{header} {/** 字符串 "some text" */}
</div>
),
};
如果将 props.header 写成类 children 结构,可视化搭建框架就会识别为组件实例,将其转化为真正的 React 实例再传给组件:
{
"componentName": "div",
"props": {
"header": [
{
"componentName": "text"
}
]
}
}
const divMeta = {
componentName: "div",
element: ({ header }) => (
<div>
{header} {/** React 组件实例,此时会渲染出组件实例 */}
</div>
),
};
这样设计是基于一个原则:组件树应该能描述出任何组件想要的 props 属性。我们反过来站在 element 角度来看,假设你注入了一个 Antd 等框架组件,如果在不改一行源码的情况下,就希望接入平台,那平台必须满足可配置出任何 props 的能力。除了基础变量外,更复杂的还有 React 组件实例与函数,现在我们解决了传组件实例的问题,至于如何传函数,我们下一小节再讲。
这样设计存在两个缺陷:
如果要解决这两个缺陷,就需要在组件元信息上定义 Props 的类型,比如:
const divMeta = {
componentName: "div",
propsType: {
header: "element",
content: ["element"],
tabs: [
{
panel: "element",
},
],
},
};
解释一下上面的例子代表的含义:
这样配合以下组件树的描述,就可以精确的将对应 element 类型转化为组件实例了,而对于基本类型 primitive 保持原样传给组件:
{
"componentName": "div",
"props": {
"header": {
"componentName": "text"
},
"names": ["a", "b", "c"],
"content": [
{
"componentName": "text"
},
{
"componentName": "text"
}
],
"tabs": [
{
"title": "tab1",
"panel": {
"componentName": "text"
}
}
]
}
}
如此一来,没有定义为 Element 的属性不会处理成 React 实例,第一个问题就自然解决了。通过配置更深层嵌套的结构,第二个问题也自然解决。
componentMeta.propsType 之所以不采用 JSONSchema 结构,是因为框架没必要内置对 props 类型校验的能力,这个能力可以交给业务层来处理,所以这里就可以采用简化版结构,方便书写,也容易阅读。
注意:propsType 中 {} 表示 value 是对象,而 [] 表示 value 是数组。为数组时,仅支持单个子元素,因为单项即是对数组每一项类型的定义。
现在已经能给 componentMeta.element 传入任意基础类型、React 实例的 props 了,现在还缺函数类型或者 Set、Map 等复杂类型问题需要解决。
由于组件树结构需要序列化入库,所以必须为一个可以序列化的 JSON 结构,而这个结构又需要暴露给开发者,所以也不适合定义一些 hack 的序列化、反序列化规则。因此要给组件 props 注入函数,需要定义在组件元信息上,由于其定义了额外的 props 属性,且不在组件树中,所以我们将其命名为 runtimeProps:
const divMeta = {
componentName: "div",
runtimeProps: () => ({
onClick: () => { console.log('click') }
})
element: ({ onClick }) => (
<button onClick={onClick}>
点击我
</button>
),
};
点击按钮后,会打印出 click。这是因为 runtimeProps 定义了函数类型 onClick 在运行时传入了组件 props。
当组件树与 componentMeta.runtimeProps 同时定义了同一个 key 时,runtimeProps 优先级更高。
本节我们介绍了组件注册与画布渲染的基础内容,我们再重新梳理一下。
首先定义了 <Designer /> API,并支持传入 componentTree 与 componentMetas,有了组件树与组件元信息,就可以实现可视化搭建画布的渲染了。
我们还介绍了如何在组件元信息定义组件的渲染函数,如何给渲染函数 props 传入基本变量、React 实例以及函数,让渲染函数可以对接任何成熟的组件库,而不需要组件库做任何适配工作。
但这只是可视化搭建的第一步,在真正开始做项目后,你还会遇到越来越多的问题,比如除了渲染画布,还要在业务层定义属性配置面板、组件拖拽列表、图层列表、撤销重做等等功能,这些功能如何拿到画布属性?如何与画布交互?runtimeProps 如何基于项目数据流给组件注入不同的属性或函数?如何根据组件 props 的变化动态注入不同函数?如何保证注入的函数引用不变?
要解决这些问题,需要在本章的基础上实现一套系统的数据流规则以及配套 API,这也是下一讲的内容。
讨论地址是:精读《组件注册与画布渲染》· Issue #464 · dt-fe/weekly
来源:https://github.com/ascoders/weekly/blob/master/可视化搭建/269.组件注册与画布渲染.md
Vuetify 支持SSR(服务端渲染),SPA(单页应用程序),PWA(渐进式Web应用程序)和标准HTML页面。 Vuetify是一个渐进式的框架,试图推动前端开发发展到一个新的水平。
通过给组件传递参数, 可以让组件变得更加可扩展, 组件内使用props接收参数,slot的使用就像它的名字一样, 在组件内定义一块空间。在组件外, 我们可以往插槽里填入任何元素。slot-scope的作用就是把组件内的数据带出来
函数子组件(FaCC )与高阶组件做的事情很相似, 都是对原来的组件进行了加强,类似装饰者。FaCC,利用了react中children可以是任何元素,包括函数的特性,那么到底是如何进行增强呢?
在现代的三大框架中,其中两个Vue和React框架,组件间传值方式有哪些?组件间的传值是灵活的,可以有多种途径,父子组件同样可以使用EventBus,Vuex或者Redux
自定义指令:以v开头,如:v-mybind。bind的作用是定义一个在绑定时执行一次的初始化动作,观察bind函数,它将指令绑定的DOM作为一个参数,在函数体中,直接操作DOM节点为input赋值。
prop的定义:在没有状态管理机制的时候,prop属性是组件之间主要的通信方式,prop属性其实是一个对象,在这个对象里可以定义一些数据,而这些数据可以通过父组件传递给子组件。 prop属性中可以定义属性的类型,也可以定义属性的初始值。
Web组件由三个独立的技术组成:自定义元素。很简单,这些是完全有效的HTML元素,包含使用一组JavaScript API制作的自定义模板,行为和标记名称(例如,<one-dialog>)。
web组件可以直接或间接的调用其他web资源。一个web组件通过内嵌返回客户端内容的另一个web资源的url来间接调用其他web资源。在执行时,一个web资源通过包含另一个资源的内容或者转发请求到另一个资源直接调用。
在实际开发项目中,有时我们会用到自定义按钮;因为一个项目中,众多的页面,为了统一风格,我们会重复用到很多相同或相似的按钮,这时候,自定义按钮组件就派上了大用场,我们把定义好的按钮组件导出,在全局引用,就可以在其他组件随意使用啦,这样可以大幅度的提高我们的工作效率。
Vue中子组件调用父组件的方法,这里有三种方法提供参考,第一种方法是直接在子组件中通过this.$parent.event来调用父组件的方法,第二种方法是在子组件里用$emit向父组件触发一个事件,父组件监听这个事件就行了。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!