很是規 - VUE 實現特定場景的主題切換

本文做者:劉文濤css

原創聲明:本文爲閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公衆號 (id: yuewen_YFE) 獲取受權,並註明做者、出處和連接。前端

實現頁面皮膚切換,常見的方案有幾種:替換 css 連接、替換 className、改變 css 原生變量值、使用 less.modifyVars、props 參數下發等; 不一樣的業務場景,咱們通常會選擇不一樣的方法來實現目標。最近在公司運營活動平臺上的主題功能的實現 ,咱們嘗試了一種新的解決方案,實現了頁面主題的切換,目標是爲了提升項目的可維護性、可擴展性,以及下降接入複雜度。數組

「主題」需求

在瞭解主題功能以前,咱們先來解下業務場景:在運營活動後臺中,編輯活動配置頁面,拖拽選擇所需對應組件,並設置組件相應配置項,點擊保存,既完成活動頁面發佈活動,前臺就能訪問對應生成活動。 編輯頁面以下圖:bash

在如上前提,咱們的需求就是:在運營後臺配置頁面中,實現全局切換主題功能,具體需求以下:less

  1. 在配置頁面,初始化頁面時,實現主題一鍵切換全部組件的樣式;
  2. 頁面中的組件的配置,可配置對應組件樣式,覆蓋主題樣式;
  3. 再次點擊設置主題,能夠覆蓋已經設置樣式的組件樣式;

實現效果以下圖所示:優化

那在瞭解完需求以後,對於 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 給予組件

接着須要在主模塊中,下發 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 改變組件樣式

組件中,獲取上級組件傳遞下來的配置參數及主題參數,並監聽 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
}
複製代碼

最終實現了最終的主題切換的效果。

該方式帶來的優點:

  1. 對組件代碼幾乎無侵入性,組件只須要修改樣式相關參數帶上特殊標示既可,規則相對簡單;
  2. 參數無需一層層下發,易於維護;
  3. 主題與主線功能相對獨立,能夠輕易移除主題功能,項目也能夠正常運行;

總結

主題的實現,不論是常見的方式,仍是上述項目中的主題的實現方式,咱們每每須要瞭解業務特性,去尋找最合適的解決方案。不一樣項目,有不一樣的實現方式,但目標都是爲了提升項目的可維護性、可擴展性,以及下降接入複雜度。

項目目前的實現方案,尚不失爲一個好的解決方案,或者能夠做爲一種新的思路,供你們參考。

更多分享,請關注 YFE:

想要了解關於團隊的更多信息,能夠查看官網哦 😄

相關文章
相關標籤/搜索