背景
活動頁,是各個網際網路公司一個頭疼的問題。為了跟上對手的腳步,需要時不時就要搞點花樣。頻繁重複的作業對於前端團隊來講是一件非常頭疼的事情。活動釋出系統是迫切需要的,讓運營人員自己通過這個配置化活動頁釋出系統完成活動的釋出。
但是配置化活動釋出系統對靈活性,擴充套件性,維護性都具有很大的挑戰。像阿里,騰訊,京東都有各自的活動釋出配置系統。但是對於我們這種小團隊人手短缺,質量又無法與巨頭相提並論,實在是不小的挑戰。更何況我們還要同時相容3個活動方的平臺(展現相同,業務邏輯和互動形式不同),對於我們來說更加是一個不小的挑戰。
技術難點
我們的技術難點並不是如何製作出精美的活動頁,而是如何去抽象各個元件的模型,以便於讓運營可以通過各種基礎UI元件來實現千變萬化活動頁滿足各種腦洞大開的活動場景。
活動頁上的一個圖片,同樣是圖片,他可以是一個平鋪的廣告入口,可以是分會場入口,可以是一個調查問卷的入口,也可以是一個報名入口,同時也可能是某個參與活動的報名入口。
使用者點選後會有多種不同的互動形式,跳轉新頁面,彈出彈層,錨點到指定位置,甚至是提交表單。
展現形式也會多種多樣,輪播,滑動,平鋪,堆疊。
針對不同的用途,難以預測的互動行為,和不同的展現形式,這將會是一個非常複雜而龐大的元件,如果我們開發成一個元件,以後運營說不定有會想出什麼倒黴點子,開發多個元件,會有很多重複,因此,我們設計的系統如何支撐他們的業務?如何儘可能減少二次開發的工作量,降低維護成本,將是這套系統需要考慮的重中之重。
設計是抽象的過程,抽象的目的是解耦和隔離
如何去構建一個易於維護,易於擴充套件的系統,其實是一件很難的事情,雖然我現在說我的設計是可以做到易於維護,易於擴充套件的,但是保不齊以後會被運營和產品的腦洞打臉。但是架構是漸進的。儘可能的抽象,解耦各個模型,而設計模式就是這方面最好的指導。
解耦
每一個元件將會有自己的行為,UI,以及自己的互動邏輯,我們可以將其分為展現形式與互動行為。展現形式是元件在客戶那裡的樣子,互動邏輯就是使用者操作的時候進行的一系列業務邏輯。這兩個邏輯單元組成一個基本的元件:
我們將一個基本元件單元分解成3個元件 -- UI元件(展現形式),互動元件(互動邏輯),元件單元(基本單位)。元件單元包含UI元件和互動元件。因此我們就可以通過使用不同的UI元件和互動元件組合的方式來組裝出來具有各種不同展現形式,不同互動邏輯的前端元件了。這一方法叫做 -- 橋接模式(實現程式碼)。即:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。同時還使用了組合模式(實現程式碼)。
因此我們對於每一個基本元件單元就可以設計一個下面的資料結構:
{
name: '元件名稱',
id: '元件ID',
type: '元件型別',
uiComp: {
name: 'UI元件名稱',
style: ''
},
logicComp: '互動元件名稱'
}
複製程式碼
使用Vue根據這個結構構建出頁面就是如下程式碼:
<template>
<Element>
<template :is="ui元件名稱" :style="元件樣式"></template>
<template :is="互動元件名稱"></template>
</Element>
</template>
<script>
export default {
...
}
</script>
複製程式碼
這裡遵循了單一職責原則,UI元件僅負責展現,互動元件負責互動反饋。實現了UI與邏輯的隔離。如果將來有新的互動邏輯,我們就增加一個邏輯元件,如果增加了UI展現,就加一個UI元件,任何UI元件都可以和任意同一個
單元元件內的
互動元件相互組合。也就滿足了里氏替換原則。
抽象
通過Vue的template元件,我們很好的實現了元件的動態組合,我們可以認為template就是一個抽象工廠(程式碼實現)。根據我們的需要為我們提供不同的元件。而在單元元件中根本不需要關心它具體是什麼。所有的基本元件單元均被抽象成一個高階元件
但是因為UI展現和邏輯是單獨的兩個不同元件,那麼我們如何將他們打通呢?因為UI元件才能接受到使用者的動作,而動作的反饋都在互動邏輯元件中。中介模式(程式碼實現),即:用一箇中介物件來封裝一系列的物件互動。中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。這個中介物件就是我們的負責組合UI元件和互動元件的元件單元元件,它通過Vue的props來向不同的元件分發後設資料與結果資料:
<template>
<Element>
<template :is="ui元件名稱" :style="元件樣式" :props="用於顯示的結果資料" @action="使用者操作回撥函式"></template>
<template :is="互動元件名稱" :props="後設資料" :payload="資料載體" @init="初始化完成回撥函式" @finish="使用者操作響應回撥"></template>
</Element>
</template>
<script>
export default {
data() {
return {
'後設資料',
'結果資料',
'資料載體'
}
},
methods: {
'初始化完成回撥函式' (payload) {
if (payload.type === 'ok') {
this.'結果資料' = payload.data;
}
},
'使用者操作回撥函式' (payload) {
if (payload.type === '幹了什麼') {
this.'資料載體' = payload.data;
}
},
'使用者操作響應回撥' (payload) {
if (payload.type === '結果狀態') {
this.'結果資料' = payload.data;
}
}
}
}
</script>
複製程式碼
業務邏輯元件:
<template>
<Element>
<Toast />
</Element>
</template>
<script>
export default {
props: {
'後設資料': Object
},
watch: {
payload: {
handle() {
this.$emit('finish', action)
},
deep: true
}
},
mounted() {
this.$emit('init', action)
}
}
</script>
複製程式碼
這裡藉助了Vue的props實現了觀察者模式(程式碼實現),子元件通過觀察props的變化來通過中介(單元元件)向各個子元件(ui元件,邏輯元件)進行通訊。同時,每一個子元件又是一個訪問者模式(程式碼實現)的訪問者實現,通過payload類似redux中action的使用,我們就起到了統一介面的目的,從而也就實現了多型。
結束
目前,這個系統還不是很強大,甚至過於簡單,有可能有些人會提出質疑,我還是那句話,架構是漸進的。隨著運營人員的腦洞越來越大,產品的膽子越來越大,這個系統也會不斷成長,裡面的東西也會變得越來越複雜。
這個設計並沒有像其他大廠那樣提供功能強大而複雜功能全面的定製化元件,而是需要通過增加新元件(水平擴充套件)的方式來對系統進行擴充套件。這主要是本著【開放封閉原則】,對修改說【不】,改展現形式,就增加新的UI元件,改業務邏輯,就增加新的邏輯元件,這樣才可以保持系統不至於過於臃腫和複雜以及難以維護和擴充套件。保持著每個元件都是很簡單,並且邏輯和顯示分離,遵循介面隔離原則。這樣任何人加入這個專案,都可以很容易的接手系統進行擴充套件。而且即很大程度上降低了活動頁的開發成本,也避免了絕大多數的重複勞動,同時延長了系統的使用週期,最大化壓榨系統的價值。