如何做一個(gè)看板搭建系統(tǒng)
如何做一個(gè)看板搭建系統(tǒng)
http://zoo.zhengcaiyun.cn/blog/article/buildingsystem
一、什么是數(shù)據(jù)看板,數(shù)據(jù)看板有什么用
在解釋數(shù)據(jù)看板概念之前,我們要先知道,什么是數(shù)據(jù)可視化。
“數(shù)據(jù)可視化被許多學(xué)科視為與視覺傳達(dá)含義相同的現(xiàn)代概念。它涉及到數(shù)據(jù)的可視化表示的創(chuàng)建和研究。為了清晰有效地傳遞信息,數(shù)據(jù)可視化使用統(tǒng)計(jì)圖形、圖表、信息圖表和其他工具??梢允褂命c(diǎn)、線或條對(duì)數(shù)字?jǐn)?shù)據(jù)進(jìn)行編碼,以便在視覺上傳達(dá)定量信息。有效的可視化可以幫助用戶分析和推理數(shù)據(jù)和證據(jù)。它使復(fù)雜的數(shù)據(jù)更容易理解和使用。- 維基百科
數(shù)據(jù)看板即是數(shù)據(jù)可視化的載體,通過合理的頁面布局、效果設(shè)計(jì)來將可視化數(shù)據(jù)更好的展現(xiàn)。
個(gè)人認(rèn)為,數(shù)據(jù)看板的作用大致為以下兩種:
1、掌握情況
通過數(shù)據(jù)呈現(xiàn),決策者們能較為清晰地掌握自己產(chǎn)品的運(yùn)營(yíng)情況。
2、問題解決
通過數(shù)據(jù)分析,能夠通過數(shù)據(jù)可視化,從動(dòng)態(tài)數(shù)據(jù)中提煉出規(guī)律,發(fā)現(xiàn)不符合預(yù)期的部分并給出修改意見。
二、配置文件數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
假設(shè)有如下這樣的一個(gè)看板頁面,讓你用一份 json 文件來記錄組件的信息,類似,寬高、位置以及標(biāo)題等等,試想一下你會(huì)怎么設(shè)計(jì) json 的數(shù)據(jù)結(jié)構(gòu)(可以不用關(guān)心具體的值是什么)。
這個(gè)問題并不是很難,相信大部分人都能設(shè)計(jì)一份自己的數(shù)據(jù)結(jié)構(gòu),比如我設(shè)計(jì)的結(jié)構(gòu)就是下面這樣:
[
{
"type": 'line', // 類型
"id": 1, // 唯一標(biāo)識(shí)
"data": [], // 數(shù)據(jù)存放
"title": "貨物銷售情況", // 組件標(biāo)題
"layout": { x: 0, y: 0, w: 3, h: 2 }, // 頁面布局信息(坐標(biāo)、寬高)
},
{
"type": 'bar',
"id": 2,
"data": [],
"title": "貨物留存情況",
"layout": { i: 'c', x: 3, y: 0, w: 3, h: 2 },
}
]
有了這樣一份數(shù)據(jù)結(jié)構(gòu)之后,接下來,我們接下來就要開發(fā)組件,比如{type:line}的配置項(xiàng)就去用 line 組件渲染,并且我們需要把數(shù)據(jù)還有一些其他配置項(xiàng)傳給組件。關(guān)于每個(gè)組件的開發(fā),這里就不深入探討了,需要考慮的就是你的內(nèi)部組件要具有接受數(shù)據(jù)并處理配置的能力。
三、組件容器
現(xiàn)在組件有了,配置也有了,接下來就很簡(jiǎn)單了,根組件直接循環(huán)遍歷下,就可以了,代碼如下:
<div>
{widgets.map(widget => {
const WidgetComp = getWidgetComp(widget.type);
return <WidgetComp config={widget} key={widget.id} />
})}
</div>
好了,大功告成,頁面渲染完成!
先別著急,我們想想看,是不是每個(gè)組件,我們都需要去做同樣的工作,比如數(shù)據(jù)請(qǐng)求、布局處理等等。
這時(shí)候,我們就需要在一個(gè)統(tǒng)一的地方去做這些同樣且重復(fù)的工作。這里我想到了兩種方式:1、公共的 util 函數(shù)。2、給所有組件增加一層包裹容器。
第一種方法雖然可以滿足我們的要求,但是還是存在局限性,那就是當(dāng)我們想要在 dom 上做一些處理的時(shí)候,還是不可避免的會(huì)有重復(fù)代碼量,比如給組件添加點(diǎn)擊事件、設(shè)置 dom 的 style 屬性等。
我個(gè)人比較推薦第二種方法,也就是組件容器。一個(gè)組件容器大概長(zhǎng)這樣:
const Widget = ({config}) => {
// 組件點(diǎn)擊事件
const handleClick=() => {/**/};
// 布局處理
const handleLayout=() => {/**/};
// 獲取組件數(shù)據(jù)
const getWidgetData=() => {/**/};
// 其他的一些公共邏輯
.......
const WidgetComp = getWidgetComp(widget.type);
return (
return <WidgetComp config={widget} />
)
}
對(duì)應(yīng)的根組件代碼可以改成這樣:
<div>
{widgets.map(widget => <Widget config={widget} key={widget.id} />)}
</div>
四、配置文件的編輯
上面的工作做完之后,我們可以做一個(gè)簡(jiǎn)單的渲染了,但是既然是搭建系統(tǒng),怎么可能僅僅滿足于渲染呢?接下來,我們要考慮下,給搭建的用戶提供一個(gè)可以修改配置的地方。為了統(tǒng)一口徑,我們把使用配置的地方,稱用戶側(cè),修改配置的地方,稱編輯側(cè)。
一個(gè)編輯側(cè)可能長(zhǎng)這樣:
大致分為三個(gè)區(qū)域,組件商店、展示區(qū)域和配置區(qū)域,這里我們先說右側(cè)的組件配置區(qū)域。
我們這里拿 line 組件 舉例。假設(shè),現(xiàn)在產(chǎn)品小姐姐給你提了第一個(gè)需求,需要這個(gè) line 組件支持修改名稱。so easy!直接寫個(gè) form 表單:
<Form form={form}>
<Item name="name" label="名稱" rules={[{ required: true, message: '請(qǐng)輸入' }]}>
<Input placeholder="請(qǐng)輸入" />
</Item>
</Form>
修改完之后,直接把新的名稱發(fā)給后端存起來就完事了。
一天后,產(chǎn)品小姐姐又提了另一個(gè)需求,bar 組件需要支持修改寬度。沒辦法,接著改,不能讓產(chǎn)品小姐姐看不起!于是你的代碼可能變成了這樣:
const renderForm = ({type}) => {
if(type === 'line') {
return (
<Item name="name" label="名稱" rules={[{ required: true, message: '請(qǐng)輸入' }]}>
<Input placeholder="請(qǐng)輸入" />
</Item>
)
} else if(type === 'bar') {
return (
<Item name="width" label="寬度" rules={[{ required: true, message: '請(qǐng)輸入' }]}>
<Input placeholder="請(qǐng)輸入" />
</Item>
)
}
}
return (<Form form={form}>
{renderForm()}
</Form>)
緊接著,產(chǎn)品小姐姐的需求與日增多,組件數(shù)量也越來越龐大,你的 if else 越寫越多....。所以我們需要一個(gè)統(tǒng)一的配置器,和一個(gè)統(tǒng)一的描述組件配置的 schema 文件。
先說組件的 schema 文件,每一個(gè)組件都配帶一個(gè) schema 文件,schema 里主要記錄當(dāng)前組件支持修改的配置項(xiàng)(fields),和當(dāng)前組件配置項(xiàng)的默認(rèn)值(models)。類似這樣:
{
"fields": [{
"label": "名稱",
"type": "input",
"name": "name"
}],
"models": {
"name": "默認(rèn)名稱"
}
}
當(dāng)點(diǎn)擊一個(gè)組件的編輯按鈕時(shí),根據(jù)組件類型獲取到對(duì)應(yīng)的 schema 文件,將組件配置項(xiàng)默認(rèn)值 models 和從后端拿到的數(shù)據(jù),做一個(gè) merge,將 merge 后的數(shù)據(jù)和 fieds 傳給配置器。
配置器要做的工作就是,根據(jù) fileds 動(dòng)態(tài)渲染表單,關(guān)于表單動(dòng)態(tài)渲染,我們團(tuán)隊(duì)有一篇不錯(cuò)的文章《表單數(shù)據(jù)形式配置化設(shè)計(jì)》(https://juejin.cn/post/7119639489567260686),大家有興趣可以參考下。
總結(jié)下編輯側(cè)的工作,第一步,拿到對(duì)應(yīng)組件的 schema 文件,傳給配置器。第二步,配置器根據(jù) fileds 動(dòng)態(tài)渲染表單,并根據(jù)后端返回?cái)?shù)據(jù)和 schema 中的默認(rèn)數(shù)據(jù),用作表單回顯。最后就是,搭建用戶修改配置項(xiàng),再把修改后的數(shù)據(jù)發(fā)送給后端保存。
五、從遠(yuǎn)程組件商店加載組件
以上已經(jīng)完成了配置的產(chǎn)出、使用和修改。接下來我們?cè)偎伎妓伎冀M件。我們現(xiàn)在的組件都是存在本地的,后期隨著組件數(shù)量的增多,頁面 js 文件肯定會(huì)越來越大,即使你使用了代碼分割,也不可避免會(huì)導(dǎo)致 build 后的包體積越來越大。
所以我們需要一個(gè)遠(yuǎn)端存放組件的地方,也就是組件商店。
那么商店既然是遠(yuǎn)端的,那把這個(gè)商店建在哪里呢?我們團(tuán)隊(duì)的解決方案就是,把每個(gè)組件打上版本號(hào)上傳到靜態(tài)服務(wù)器上這樣,版本號(hào)的作用這里先不管,這樣不管在編輯側(cè)還是用戶側(cè),我們可以根據(jù)項(xiàng)目的配置數(shù)據(jù)遠(yuǎn)程加載組件,關(guān)于遠(yuǎn)程組件的加載方案,可以參考下我們團(tuán)隊(duì)另一篇寫的不錯(cuò)的文章《淺談低代碼平臺(tái)遠(yuǎn)程組件加載方案》(https://juejin.cn/post/7127440050937151525)。
當(dāng)然,一個(gè)組件商店不僅于此,我們目前只實(shí)現(xiàn)了一些基本功能,一個(gè)完整的組件商店功能包括:
組件線上編輯(上傳)模塊。
組件審核模塊。
組件更新/發(fā)布模塊。
組件管理(上架/下架/刪除/下載/版本)。
六、靜態(tài)化
現(xiàn)在我們?cè)诰庉媯?cè)修改提交,用戶側(cè)就能實(shí)時(shí)地得到修改后的配置了,對(duì)應(yīng)的頁面刷新就會(huì)變化。
可是這樣會(huì)導(dǎo)致一個(gè)問題,假設(shè)以前的老版本 v1.0 需要修改到新版本 v2.0,并且工作量很大,需要兩天才能完成,那么你第一天只能改一半,第二天早上你準(zhǔn)備改另一半的時(shí)候,可能你們公司的投訴電話已經(jīng)被打爆了,因?yàn)椋愕目蛻粼缟洗蜷_頁面的時(shí)候,是你第一天只改了一半配置的頁面。
怎么解決這個(gè)問題呢?答案是增加一個(gè)發(fā)布操作,只有發(fā)布過后,你修改的配置才會(huì)在用戶側(cè)生效。
但是這樣還是會(huì)有問題,假設(shè)你在項(xiàng)目 B 修改了一個(gè)組件,該組件在項(xiàng)目 A 也用到了,那一旦該組件引發(fā)了 bug,項(xiàng)目 A,項(xiàng)目 B都發(fā)出問題,如果是十幾個(gè)項(xiàng)目,那就會(huì)引起十幾個(gè)項(xiàng)目的問題。
基于此,我們需要思考一種方法,針對(duì)發(fā)布過后的項(xiàng)目,即使對(duì)應(yīng)組件有修改,也不會(huì)影響到它們,我們團(tuán)隊(duì)使用的解決方案就是組件靜態(tài)化。
大致思路就是,發(fā)布時(shí),首先獲取對(duì)應(yīng)的項(xiàng)目配置,根據(jù)項(xiàng)目配置獲取組件列表。然后根據(jù)列表,獲取遠(yuǎn)程組件商店的 js 文件,將獲取到的 js 文件插入到對(duì)應(yīng)的 html 模版中。最后,將拼裝好的 html 放到靜態(tài)服務(wù)器上。
這樣,后續(xù)用戶側(cè)只需要訪問靜態(tài)服務(wù)器上的 html 就可以了,即使組件也修改,已發(fā)布的項(xiàng)目只要不重新發(fā)布,就不會(huì)影響已經(jīng)發(fā)布的項(xiàng)目。
“Tips: 進(jìn)一步的話,我們可以把項(xiàng)目配置也做成靜態(tài)化,這樣,第一可以省去后端同學(xué)同步兩份不同環(huán)境數(shù)據(jù)的工作量,但對(duì)于前端來說,就是順手的事。第二,方便前端自己管理已發(fā)布的配置數(shù)據(jù)。
但是如果組件修改,后續(xù)已發(fā)布的項(xiàng)目也需要重新發(fā)布呢?怎么盡量減少發(fā)布風(fēng)險(xiǎn)呢?這個(gè)就需要做組件的版本管理和容器的版本管理了。
七、組件和容器的版本管理
前面我們?cè)诮榻B組件商店的時(shí)候說到,上傳組件的時(shí)候,我們給對(duì)應(yīng)組件打上了版本號(hào),后續(xù)組件有修改的時(shí)候,修改過的組件,會(huì)被打上新的版本號(hào)。這樣,針對(duì)已發(fā)布的項(xiàng)目,只要不更新對(duì)應(yīng)組件的版本號(hào),便不會(huì)影響對(duì)應(yīng)的項(xiàng)目組件了。
那為什么要做容器的版本管理呢?前面我們介紹了組件容器的作用就是處理組件的通用邏輯,如果說組件的修改有可能會(huì)影響到其他項(xiàng)目,那么組件容器的修改就是一定會(huì)影響到其他項(xiàng)目。所以容器的版本管理比組件的版本管理更加重要。思路其實(shí)跟組件差不多,我們可以把容器理解成一個(gè)特殊的組件,跟組件不同的是,這個(gè)組件不在配置文件解析出來的組件列表中。
八、組件之間的通信
在真實(shí)的搭建場(chǎng)景中,避免不了的就是,組件之間需要通信,比如,點(diǎn)擊組件 A,組件 B 需要做出對(duì)應(yīng)的響應(yīng)。我們?cè)谶@里借鑒的是【發(fā)布-訂閱】。
我們?cè)O(shè)計(jì)一個(gè)事件調(diào)度中心,可以處理所有的事件注冊(cè)與事件響應(yīng)。訂閱者發(fā)布對(duì)應(yīng)的事件,事件調(diào)度中心負(fù)責(zé)將事件放入事件池中。發(fā)布者負(fù)責(zé)在對(duì)應(yīng)的時(shí)機(jī)觸發(fā)對(duì)應(yīng)事件,上面例子中,組件 A 就是發(fā)布者,當(dāng)組件 A 被點(diǎn)擊的時(shí)候,就是觸發(fā)事件的對(duì)應(yīng)時(shí)機(jī)。事件在調(diào)度中心出發(fā)后,再將結(jié)果反饋給訂閱者。訂閱者拿到反饋結(jié)果做出行為。
關(guān)于事件的配置描述的數(shù)據(jù)結(jié)構(gòu),形式有很多種,這里貼一份我們目前項(xiàng)目中使用的數(shù)據(jù)結(jié)構(gòu):
export default {
publish:[ // 事件發(fā)布
{
name: "電子賣場(chǎng)預(yù)警發(fā)布/流程/onChange", // 發(fā)布事件名
reflectName: "onChange" // 什么行為觸發(fā)該事件
}
],
subscribe: [ // 事件訂閱
{
list: ["電子賣場(chǎng)預(yù)警發(fā)布/onChange"], // 訂閱的事件集合
action: "setClass", // 事件行為,就是你訂閱的事件被觸發(fā)后,你要干什么
handler: "function parse(params) {? return {};? }" // 結(jié)合事件行為與該事件的返回值,組件做出自身行為
}
]
}
九、參考
1、《如何設(shè)計(jì)可視化搭建平臺(tái)的組件商店》(https://juejin.cn/post/6986824393653485605)
作者:立航
歡迎關(guān)注微信公眾號(hào) :前端民工