diff --git a/helper.js b/helper.js index 8f58889a..07b59774 100644 --- a/helper.js +++ b/helper.js @@ -13,6 +13,7 @@ const dirs = [ "源码解读", "商业思考", "算法", + "可视化搭建", "SQL" ]; diff --git a/readme.md b/readme.md index 7d9c5254..28f1e272 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ 前端界的好文精读,每周更新! -最新精读:267.精读《磁贴布局 - 性能优化》 +最新精读:268.精读《如何抽象可视化搭建》 素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2) @@ -299,6 +299,10 @@ - 201.精读《算法 - 二叉树》 - 203.精读《算法 - 二叉搜索树》 +### 可视化搭建 + +- 268.精读《如何抽象可视化搭建》 + ### SQL - 231.SQL 入门 diff --git "a/\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272/268.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\346\212\275\350\261\241\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272\343\200\213.md" "b/\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272/268.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\346\212\275\350\261\241\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272\343\200\213.md" new file mode 100644 index 00000000..a6f1ca43 --- /dev/null +++ "b/\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272/268.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\346\212\275\350\261\241\345\217\257\350\247\206\345\214\226\346\220\255\345\273\272\343\200\213.md" @@ -0,0 +1,103 @@ +在做任何可视化搭建项目时,第一步都要思考如何抽象。 + +如果不抽象,当搭建项目做到后期可能会出现 API 杂乱,难以维护的问题;做到一半甚至会怀疑为什么需要一个搭建框架,怀疑把框架去掉会不会效率更高;在后期发现不能自然的水平拓展到仪表盘、大屏、表单搭建场景等。 + +所以如果在维护一套可视化搭建系统时,不管这个系统的上层是 BI、大屏、表单填报,还是脑图也好,无论是什么,都要先思考一下这些系统背后的底层是什么,需不需要抽象,抽象的意义和价值在哪。 + +以下结合笔者的经验,尝试给出一种思考角度。 + +## 精读 + +### 什么是可视化搭建 + +表单搭建、中后台应用搭建、BI 仪表盘搭建、大屏搭建都算可视化搭建,因为它们都是在一个画布上拖拖拽拽完成的。 + +那么组件配置表单算搭建吗?聚焦单组件分析的可视化探索呢?幻灯片呢? + +比如组件配置表单,它基于 UI 组件树抽象的话,就是可视化搭建,但如果基于表单结构抽象,就是 JsonSchema,但真的所有业务场景都是数据完全映射 UI 吗?不一定,因为 UI 可以为了用户操作方便而加入更多辅助元素,甚至把一个属性拆成多个 UI 填写,所以基于可视化搭建,也就是 UI 组件树抽象的一定可以覆盖所有表单场景,但不一定是描述效率最高的方式。 + +如果每种可视化搭建场景都定义一套协议与实现,那按照搭建平台的复杂度,想同时维护两个类搭建平台的成本一定是两倍,而且不同维护人员很难交流。又或者某些可以按照搭建思路解决的场景,因为实现时经验不足,没有进行抽象,甚至进行了另一套定制抽象,回过头来看可能积重难返,团队不得不接受多套笨重实现的现状。 + +所以建议将这些场景都视为可视化搭建场景,用一套接口描述结构、API 方法,让看似百花齐放的编辑器之下拥有统一的上下文与实现。 + +### 可视化搭建的分层 + +对于不同种类的可视化搭建平台,我们尝试寻找其分层设计的最大公约数。如果把可视化搭建底层设定为逻辑层,即这个层是 UI 无关的,仅关心组件树结构、逻辑功能,那么对于每种平台的分层应该是这样的: + +1. 表单搭建:逻辑层、表单联动协议层、表单控件、业务层。 +2. 中后台应用搭建:逻辑层、应用联动协议层、应用控件、业务层。 +3. BI 仪表盘:逻辑层、筛选联动协议层、可视化控件、业务层。 +4. 大屏搭建:逻辑层、画布编辑控制器层、可视化控件和基础图形控件、业务层。 + +最底层的逻辑层应该可以统一所有类型搭建系统,并成为开发人员统一上下文的。它可以包含以下基础能力: + +1. 定义组件树结构。 +2. 定义组件元信息。 +3. 按照组件树结构递归渲染画布。 +4. 支持布局、取数、联动、筛选、校验等一系列拓展能力,业务可根据需要定制。 +5. 提供所有业务层都需要的能力,比如性能优化的组件冻结、状态管理、对组件树增删改查的 API。 + +在逻辑层完备后,再开发上层应用就会轻松很多,只要注册组件、根据业务需要在组件树初始化或组件初始化,或组件元信息注册时添加定制逻辑,与系统功能对接,并补充业务特色的如自定义布局能力,这样就可以用简单的三言两语说清楚整个系统是如何设计的。 + +### 逻辑层存在的必要性 + +再回到问题的根源:对逻辑层做统一的抽象到底是不是多余的? + +要回答这个问题,需要先了解我们手头里有哪些工具:基础开发工具 html、js、css,并且 html 也提供了一套标准化的 xml 结构;vue、react 等开发框架,基础组件、应用生命周期与事件定义。理论上基于这些,我们就可以直接上手写一个可视化搭建平台了,似乎也可以不抽象。但真正要上手时,一定会遇到以下几个通用问题需要处理: + +**定义组件树结构** + +无论做表单搭建、报表搭建、大屏搭建还是脑图画布,第一个想到的肯定是如何描述这个画布结构,而无论画布是横着排还是竖着排,横竖都是一棵树。HTML 树不能直接搬过来,一是 HTML 树的完整结构太大而我们需要的更精简的结构,二是业务层框架一般都先有一套虚拟树再转化为 dom 树,因果关系也没法反过来。而这棵树也完全可以做最大程度的抽象,即定义组件 ID、组件名、属性(Props)、子节点。 + +**定义对组件树增删改查函数** + +有了组件树肯定需要对其进行增删改查操作,因为无法基于 document API,上层框架如 vue、react 也不提供对任何标准组件树的增删改查 API,这部分能力势必要手动实现。 + +**生命周期** + +假设完全依赖 React 框架提供的组件生命周期,是可以完成大部分业务逻辑,但这意味着定义不够精细化。比方说,我们在组件 Mount 的实际监听了联动、实现取数、设置冻结等等效果,虽然也可以实现,但会遇到要不要抽象的问题: + +- 如果不抽象,业务代码就会乱糟糟的,比较难读。 +- 如果抽象,就要把联动、取数、冻结等等模块归类,封装成函数,甚至可以提供主动调用机制,UI 与逻辑解耦,但当业务层精细的去做这件事就会发现,这就是在做框架层的抽象工作,所以还不如一开始就把这些生命周期抽象到框架里。 + +逻辑层有两个核心结构,第一个是组件树结构,包含了对每个组件实例的定义;第二个是组件元信息结构,包含了对每个组件的元信息描述,大概如下图所示: + + + +逻辑层的难点就是在元信息定义足够多、足够通用的生命周期回调函数,并且这些回调函数还能尽可能的功能正交。 + +**组件渲染** + +通常一棵树按照 json 结构描述自顶向下自动渲染就可以了,但也有一些时候,比如内嵌一个富文本组件,而富文本内又嵌入一些画布组件,这些组件需要像普通画布组件一样可交互,此时就有 **渲染一个不存在于组件树的组件实例** 的需求,而这样的动态组件又要无感知的满足上面所说的各类生命周期,这也是不小的工作量。 + +**功能的拓展抽象** + +等可视化搭建平台正式维护时,就至少会遇到组件版本升级、不同类型的布局方案对接、三方组件注册等需求,这些功能如何加入到现有的搭建平台,而不让其他功能感知,是需要精心设计的。如果逻辑层把这一点抽象好,在每个功能设计一个钩子,实现一个功能时无需感知其他功能,那平台的功能拓展就会保持一个恒定的速度,不随功能增加而变得难以维护。 + +可见,可视化搭建不断迭代的过程就是自身不断抽象的过程,逻辑层实现的好坏直接影响到后期的维护性与拓展性,所以好好设计逻辑层可以让开发事半功倍。 + +### 组件配置表单要不要用搭建方案做 + +组件配置直接用表单方案而不是搭建,似乎是最容易想到的。但当每个组件都要自定义配置,我们就不得不选择基于 JsonSchema 描述的表单方案,但这与搭建应用本身的技术栈割裂了,随着联动功能的要求越来越多,会越来越发现小小的表单渲染引擎维护得越来越复杂,甚至复杂度与画布不分上下,此时再叹息两边技术栈不统一就已经晚了。 + +换个角度想一下,搭建应用不也要考虑组件间联动吗?从表单值能力来看,搭建场景并不要求每个组件都拥有一个值,反倒是可以将组件任意 props 属性看作表单值更具有 “弹性”,我们可以拓展任意 Key 作为表单值。 + +另外,从数据结构触发来描述表单看似很美好,但当表单变得越来越复杂,UI 越来越定制后,势必引入新的 UI 节点或者新的结构描述,与其后期拓展到一个不纯净的 JsonSchema 结构,不如一开始就放弃这个幻想,用 UI 组件树结构描述表单,这样事情就变得简单了:“先描述组件树,再定义每个节点分别用什么组件渲染,响应表单的哪部分 Key”。 + +## 总结 + +总结一下,回到主题,抽象可视化搭建的方法是分层:以逻辑层打底,提供一套标准规范与 API 接口,上层注册组件、实现布局,一切围绕着标准化的逻辑层进行拓展。 + +而可视化搭建的每一层都可以分别写单元测试,保证最终变化的代码只有业务层的对接部分,应用的稳定性就提高了。 + +最后提一个思考题:你是觉得可视化搭建应该如何抽象?如果想要做到每一层独立正交,你会如何设计 API 呢? + +> 讨论地址是:[精读《如何抽象可视化搭建》· Issue #463 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/463) + +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** + +> 关注 **前端精读微信公众号** + + + +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))