本文做者:劉文濤css
原創聲明:本文爲閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公衆號 (id: yuewen_YFE) 獲取受權,並註明做者、出處和連接。前端
實現頁面皮膚切換,常見的方案有幾種:替換 css 連接、替換 className、改變 css 原生變量值、使用 less.modifyVars、props 參數下發等; 不一樣的業務場景,咱們通常會選擇不一樣的方法來實現目標。最近在公司運營活動平臺上的主題功能的實現 ,咱們嘗試了一種新的解決方案,實現了頁面主題的切換,目標是爲了提升項目的可維護性、可擴展性,以及下降接入複雜度。數組
在瞭解主題功能以前,咱們先來解下業務場景:在運營活動後臺中,編輯活動配置頁面,拖拽選擇所需對應組件,並設置組件相應配置項,點擊保存,既完成活動頁面發佈活動,前臺就能訪問對應生成活動。 編輯頁面以下圖:bash
在如上前提,咱們的需求就是:在運營後臺配置頁面中,實現全局切換主題功能,具體需求以下:less
實現效果以下圖所示:優化
那在瞭解完需求以後,對於 VUE 項目,要實現主題功能,通常想到的實現方式就是 theme 參數經過 prop 下發來實現。 那咱們就先來聊下常規實現方式:prop 下發實現方式。ui
首先我定義 theme.js 爲主題相關參數,以下:this
const DEFAULT_THEME = {
primary: '#2F54EB',
subPrimary: '#D6E4FF',
error: '#F5222D',
success: '#52C41A',
warning: '#FAAD14',
background: '#FFFFFF',
text: '#222222'
}
export default {
DEFAULT: DEFAULT_THEME,
FIRST: {
...DEFAULT_THEME,
background: '#2590ff'
}
}
複製代碼
接着須要在主模塊中,下發 theme 參數,和組件相關配置參數 給到組件,點擊按鈕,切換主題:spa
<template>
<div>
<div @click="changeTheme">換主題</div>
<Component
v-for="(item, index) in componentList"
:theme="theme"
:key="index"
...item.config // 業務相關參數都在config中
/>
</div>
</template>
<script>
import theme from 'theme.js'
export default {
name: "themeChange",
data() {
return {
theme: theme['DEFAULT']
}
},
methods: {
changeTheme() {
this.theme = theme['FIRST']
}
}
}
</script>
複製代碼
組件中,獲取上級組件傳遞下來的配置參數及主題參數,並監聽 theme 的變化,當發生改變,重置樣式參數值爲主題樣式:prototype
<template>
<div>
<div :style="{ background: config.bgColor }">主題</div>
</div>
</template>
<script>
import theme from 'theme.js'
const initTheme = theme['DEFAULT']
export default {
name: "themeSwitch",
props: {
theme: {
type: Object,
default: () => ({})
},
bgColor: {
type: String,
default: initTheme.background
},
...
},
data() {
return {
config: {
bgColor: this.bgColor,
...
}
}
},
watch: {
'theme' (to, from) {
this.config.bgColor = this.theme.bgColor
}
}
}
</script>
複製代碼
看到這裏你們會說,爲何須要在 watch 中監聽主題的變化,而不是在組件初始化的時候參數就直接指向主題對應的參數呢?
由於主題需求裏面所說的,在組件裏面也是能夠改變組件相關樣式的,上述 demo 代碼中的 bgColor 參數,既能夠經過點擊切換主題能夠設置 ,也能夠是組件本身設置的,有多個來源(這裏不對組件的配置實現作詳細展開);要作到設置主題的時候,組件的樣式會設置相應的主題色,就須要在 watch 中進行監聽 theme 參數的變化,發生變化,重置相應參數,可是這種方式在每一個組件都須要有相同代碼片斷,監聽參數,達到咱們的效果,代碼很是冗餘。
綜上,咱們對代碼進一步優化,把監聽 theme 參數的方法統一封裝,這裏會有另外一個問題:每一個組件對應顏色的參數是不可定的,且參數層級也是不可定的,幾乎每一個組件須要維護一整個變量數組。這樣定義的規則會相對複雜,維護成本太高,且極易弄錯。
很顯然這樣的實現方式並非一種很好的方法,那要如何實現?
在嘗試上面的方式以後,我在想個人思路是否正確,是否是切入角度有點問題,那咱們換一個角度去切入。
當我們發現頁面整個 this.componentList 參數 ( 裏面存儲了全部組件的相關配置 ) 我是能夠拿到的時候,我們是否是能夠從數據入手? ok,說到這裏,那其實思路就出現了, this.componentList 裏面的參數規則:
[
{
componentName: 'xx',
config: {
color: 'xxx',
background: 'xxx',
...
}
},
...
]
複製代碼
咱們會發現,在開發組件的時候就已是把顏色相關參數提取到配置裏面了,那也就是說我修改配置參數的值,其實就能夠達到設置主題的效果? 由於全部組件的配置參數都是由this.componentList 參數下發的。
定義 theme.js 相關參數,和上面一致,故不在多說,主要作的就是,在組件中,咱們把相關參數進行修改,改成有特殊標示的參數, 以下:
<template>
<div>
<div :style="{ background: this['bgColor.t.background']}">主題</div>
</div>
</template>
<script>
import theme from 'theme.js'
const initTheme = theme['DEFAULT']
export default {
name: "themeSwitch",
props: {
'bgColor.t.background': { // .t.: 爲特殊標識 ;background: 爲主題裏面對應的字段名 background: '#FFFFFF'
type: String,
default: initTheme.background
}
}
}
</script>
複製代碼
當點擊主題切換的時候,會去遍歷 this.componentList 參數,修改有特殊標示的參數爲新主題對應的參數,代碼以下:
/*
* 根據主題重製componentsConfig
* @method changeTheme
* */
changeTheme () {
this.theme = theme['FIRST']
this.componentList.forEach(component => {
this._setThemeChangeConfig(component.config || {})
})
},
_setThemeChangeConfig (obj) {
Object.keys(obj).map(name => {
if (Object.prototype.toString.call(obj[name]) === '[object Object]') {
this._setThemeChangeConfig(obj[name])
} else {
const themeColorArr = name.match(/\.t\.(\S*)/)
if (themeColorArr && this.isThemeColorName(themeColorArr[1])) {
this.$set(obj, name, this.theme[themeColorArr[1]])
}
}
})
},
/*
* 判斷顏色name是否在主題裏面
* @method isThemeColorName
* */
isThemeColorName (name) {
let has = false
Object.keys(this.theme).forEach((paramsName) => {
if (paramsName === name) has = true
})
return has
}
複製代碼
最終實現了最終的主題切換的效果。
該方式帶來的優點:
主題的實現,不論是常見的方式,仍是上述項目中的主題的實現方式,咱們每每須要瞭解業務特性,去尋找最合適的解決方案。不一樣項目,有不一樣的實現方式,但目標都是爲了提升項目的可維護性、可擴展性,以及下降接入複雜度。
項目目前的實現方案,尚不失爲一個好的解決方案,或者能夠做爲一種新的思路,供你們參考。