本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
👆 这是第 172 篇不掺水的原创,想要了解更多,请戳下方卡片关注我们吧~
如何做一个看板搭建系统
http://zoo.zhengcaiyun.cn/blog/article/buildingsystem
一、什么是数据看板,数据看板有什么用
在解释数据看板概念之前,我们要先知道,什么是数据可视化。
“
数据可视化被许多学科视为与视觉传达含义相同的现代概念。它涉及到数据的可视化表示的创建和研究。为了清晰有效地传递信息,数据可视化使用统计图形、图表、信息图表和其他工具。可以使用点、线或条对数字数据进行编码,以便在视觉上传达定量信息。有效的可视化可以帮助用户分析和推理数据和证据。它使复杂的数据更容易理解和使用。- 维基百科
数据看板即是数据可视化的载体,通过合理的页面布局、效果设计来将可视化数据更好的展现。
个人认为,数据看板的作用大致为以下两种:
1、掌握情况
通过数据呈现,决策者们能较为清晰地掌握自己产品的运营情况。
2、问题解决
通过数据分析,能够通过数据可视化,从动态数据中提炼出规律,发现不符合预期的部分并给出修改意见。
二、配置文件数据结构设计
假设有如下这样的一个看板页面,让你用一份 json 文件来记录组件的信息,类似,宽高、位置以及标题等等,试想一下你会怎么设计 json 的数据结构(可以不用关心具体的值是什么)。
这个问题并不是很难,相信大部分人都能设计一份自己的数据结构,比如我设计的结构就是下面这样:
[ { "type": 'line', // 类型 "id": 1, // 唯一标识 "data": [], // 数据存放 "title": "货物销售情况", // 组件标题 "layout": { x: 0, y: 0, w: 3, h: 2 }, // 页面布局信息(坐标、宽高) }, { "type": 'bar', "id": 2, "data": [], "title": "货物留存情况", "layout": { i: 'c', x: 3, y: 0, w: 3, h: 2 }, }]有了这样一份数据结构之后,接下来,我们接下来就要开发组件,比如{type:line}的配置项就去用 line 组件渲染,并且我们需要把数据还有一些其他配置项传给组件。关于每个组件的开发,这里就不深入探讨了,需要考虑的就是你的内部组件要具有接受数据并处理配置的能力。
三、组件容器
现在组件有了,配置也有了,接下来就很简单了,根组件直接循环遍历下,就可以了,代码如下:
<div> {widgets.map(widget => { const WidgetComp = getWidgetComp(widget.type); return <WidgetComp config={widget} key={widget.id} /> })}</div>好了,大功告成,页面渲染完成!
先别着急,我们想想看,是不是每个组件,我们都需要去做同样的工作,比如数据请求、布局处理等等。
这时候,我们就需要在一个统一的地方去做这些同样且重复的工作。这里我想到了两种方式:1、公共的 util 函数。2、给所有组件增加一层包裹容器。
第一种方法虽然可以满足我们的要求,但是还是存在局限性,那就是当我们想要在 dom 上做一些处理的时候,还是不可避免的会有重复代码量,比如给组件添加点击事件、设置 dom 的 style 属性等。
我个人比较推荐第二种方法,也就是组件容器。一个组件容器大概长这样:
const Widget = ({config}) => { // 组件点击事件 const handleClick=() => {/**/}; // 布局处理 const handleLayout=() => {/**/}; // 获取组件数据 const getWidgetData=() => {/**/}; // 其他的一些公共逻辑 ....... const WidgetComp = getWidgetComp(widget.type); return ( return <WidgetComp config={widget} /> )}对应的根组件代码可以改成这样:
<div> {widgets.map(widget => <Widget config={widget} key={widget.id} />)}</div>四、配置文件的编辑
上面的工作做完之后,我们可以做一个简单的渲染了,但是既然是搭建系统,怎么可能仅仅满足于渲染呢?接下来,我们要考虑下,给搭建的用户提供一个可以修改配置的地方。为了统一口径,我们把使用配置的地方,称用户侧,修改配置的地方,称编辑侧。
一个编辑侧可能长这样:
大致分为三个区域,组件商店、展示区域和配置区域,这里我们先说右侧的组件配置区域。
我们这里拿 line 组件 举例。假设,现在产品小姐姐给你提了第一个需求,需要这个 line 组件支持修改名称。so easy!直接写个 form 表单:
<Form form={form}> <Item rules={[{ required: true, message: '请输入' }]}> <Input placeholder="请输入" /> </Item></Form>修改完之后,直接把新的名称发给后端存起来就完事了。
一天后,产品小姐姐又提了另一个需求,bar 组件需要支持修改宽度。没办法,接着改,不能让产品小姐姐看不起!于是你的代码可能变成了这样:
const renderForm = ({type}) => { if(type === 'line') { return ( <Item rules={[{ required: true, message: '请输入' }]}> <Input placeholder="请输入" /> </Item> ) } else if(type === 'bar') { return ( <Item rules={[{ required: true, message: '请输入' }]}> <Input placeholder="请输入" /> </Item> ) }}return (<Form form={form}> {renderForm()}</Form>)紧接着,产品小姐姐的需求与日增多,组件数量也越来越庞大,你的 if else 越写越多....。所以我们需要一个统一的配置器,和一个统一的描述组件配置的 schema 文件。
先说组件的 schema 文件,每一个组件都配带一个 schema 文件,schema 里主要记录当前组件支持修改的配置项(fields),和当前组件配置项的默认值(models)。类似这样:
{ "fields": [{ "label": "名称", "type": "input", "name": "name" }], "models": { "name": "默认名称" }}当点击一个组件的编辑按钮时,根据组件类型获取到对应的 schema 文件,将组件配置项默认值 models 和从后端拿到的数据,做一个 merge,将 merge 后的数据和 fieds 传给配置器。
配置器要做的工作就是,根据 fileds 动态渲染表单,关于表单动态渲染,我们团队有一篇不错的文章《表单数据形式配置化设计》(https://juejin.cn/post/7119639489567260686), 大家有兴趣可以参考下。
总结下编辑侧的工作,第一步,拿到对应组件的 schema 文件,传给配置器。第二步,配置器根据 fileds 动态渲染表单,并根据后端返回数据和 schema 中的默认数据,用作表单回显。最后就是,搭建用户修改配置项,再把修改后的数据发送给后端保存。
五、从远程组件商店加载组件
以上已经完成了配置的产出、使用和修改。接下来我们再思考思考组件。我们现在的组件都是存在本地的,后期随着组件数量的增多,页面 js 文件肯定会越来越大,即使你使用了代码分割,也不可避免会导致 build 后的包体积越来越大。
所以我们需要一个远端存放组件的地方,也就是组件商店。
那么商店既然是远端的,那把这个商店建在哪里呢?我们团队的解决方案就是,把每个组件打上版本号上传到静态服务器上这样,版本号的作用这里先不管,这样不管在编辑侧还是用户侧,我们可以根据项目的配置数据远程加载组件,关于远程组件的加载方案,可以参考下我们团队另一篇写的不错的文章《浅谈低代码平台远程组件加载方案》(https://juejin.cn/post/7127440050937151525)。
当然,一个组件商店不仅于此,我们目前只实现了一些基本功能,一个完整的组件商店功能包括:
组件线上编辑 (上传) 模块。
组件审核模块。
组件更新 / 发布模块。
组件管理 (上架 / 下架 / 删除 / 下载 / 版本)。
六、静态化
现在我们在编辑侧修改提交,用户侧就能实时地得到修改后的配置了,对应的页面刷新就会变化。
可是这样会导致一个问题,假设以前的老版本 v1.0 需要修改到新版本 v2.0,并且工作量很大,需要两天才能完成,那么你第一天只能改一半,第二天早上你准备改另一半的时候,可能你们公司的投诉电话已经被打爆了,因为,你的客户早上打开页面的时候,是你第一天只改了一半配置的页面。
怎么解决这个问题呢?答案是增加一个发布操作,只有发布过后,你修改的配置才会在用户侧生效。
但是这样还是会有问题,假设你在项目 B 修改了一个组件,该组件在项目 A 也用到了,那一旦该组件引发了 bug,项目 A,项目 B 都发出问题,如果是十几个项目,那就会引起十几个项目的问题。
基于此,我们需要思考一种方法,针对发布过后的项目,即使对应组件有修改,也不会影响到它们,我们团队使用的解决方案就是组件静态化。
大致思路就是,发布时,首先获取对应的项目配置,根据项目配置获取组件列表。然后根据列表,获取远程组件商店的 js 文件,将获取到的 js 文件插入到对应的 html 模版中。最后,将拼装好的 html 放到静态服务器上。
这样,后续用户侧只需要访问静态服务器上的 html 就可以了,即使组件也修改,已发布的项目只要不重新发布,就不会影响已经发布的项目。
“
Tips: 进一步的话,我们可以把项目配置也做成静态化,这样,第一可以省去后端同学同步两份不同环境数据的工作量,但对于前端来说,就是顺手的事。第二,方便前端自己管理已发布的配置数据。
但是如果组件修改,后续已发布的项目也需要重新发布呢?怎么尽量减少发布风险呢?这个就需要做组件的版本管理和容器的版本管理了。
七、组件和容器的版本管理
前面我们在介绍组件商店的时候说到,上传组件的时候,我们给对应组件打上了版本号,后续组件有修改的时候,修改过的组件,会被打上新的版本号。这样,针对已发布的项目,只要不更新对应组件的版本号,便不会影响对应的项目组件了。
那为什么要做容器的版本管理呢?前面我们介绍了组件容器的作用就是处理组件的通用逻辑,如果说组件的修改有可能会影响到其他项目,那么组件容器的修改就是一定会影响到其他项目。所以容器的版本管理比组件的版本管理更加重要。思路其实跟组件差不多,我们可以把容器理解成一个特殊的组件,跟组件不同的是,这个组件不在配置文件解析出来的组件列表中。
八、组件之间的通信
在真实的搭建场景中,避免不了的就是,组件之间需要通信,比如,点击组件 A,组件 B 需要做出对应的响应。我们在这里借鉴的是【发布 - 订阅】。
我们设计一个事件调度中心,可以处理所有的事件注册与事件响应。订阅者发布对应的事件,事件调度中心负责将事件放入事件池中。发布者负责在对应的时机触发对应事件,上面例子中,组件 A 就是发布者,当组件 A 被点击的时候,就是触发事件的对应时机。事件在调度中心出发后,再将结果反馈给订阅者。订阅者拿到反馈结果做出行为。
关于事件的配置描述的数据结构,形式有很多种,这里贴一份我们目前项目中使用的数据结构:
export default { publish:[ // 事件发布 { name: "电子卖场预警发布/流程/onChange", // 发布事件名 reflectName: "onChange" // 什么行为触发该事件 } ], subscribe: [ // 事件订阅 { list: ["电子卖场预警发布/onChange"], // 订阅的事件集合 action: "setClass", // 事件行为,就是你订阅的事件被触发后,你要干什么 handler: "function parse(params) {↵ return {};↵ }" // 结合事件行为与该事件的返回值,组件做出自身行为 } ]}九、参考
1、《如何设计可视化搭建平台的组件商店》(https://juejin.cn/post/6986824393653485605)
看完两件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我两件小事
点个「在看」,让更多人也能看到这篇内容(点了「在看」,bug -1 😊)
关注公众号「政采云前端」,持续为你推送精选好文
招贤纳士
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云研发部,Base 在风景如画的杭州。团队现有 80 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的 “老” 兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是 “5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com