前言
在很多業務場景中,我們的style
樣式可能會根據業務邏輯的變化而變化,這個時候大家最容易想到的方案就是多寫幾個class
類,根據不同場景應用不同的類,比如這樣:
<div
:class="{
[$style.sign_day]: true,
[$style.sign_today]: getSignStatus(item) == 1,
[$style.sign_notyet_day]: getSignStatus(item) == 6,
[$style.sign_day_dark]: theme == 'dark',
}"
>
</div>
<style lang="scss" module>
.sign_day {
background: red;
}
.sign_today {
background: yellow;
}
.sign_notyet_day {
background: blue;
}
.sign_day_dark {
background: orange;
}
</style>
這樣雖然也是一種不錯的方式,但是如果型別有非常多的話,那麼你就得在vue
模版裡面寫大量的判斷表示式,並且在style
中寫大量的class
類。
要是在style
中也可以直接使用script
中的JS變數,那麼這種場景處理起來是不是會更方便一點呢?
Vue2 CSS變數
在Vue2
中,遇到以上業務場景如果我們不想寫大量的class
類的話,可以藉助css
中的var()
函式來實現
var()
可以插入一個自定義屬性(有時也被稱為“CSS 變數”)的值,用來代替非自定義屬性中值的任何部分。
比如:
在模版中呼叫getStyle
函式獲取顏色值,並且定義成css變數
<div
v-for="item in signList"
:key="item.day"
:class="$style.sign_day"
:style="{ '--color': getStyle(item) }"
>
{{ item.title }}
</div>
生成顏色值
getStyle(item) {
switch (item.status) {
case 0:
return '#f8ae00'
case 1:
return '#e5353e'
case 2:
return '#1fddf4'
case 3:
return '#1ff46a'
default:
return '#191919'
}
},
然後就可以只寫一個css類
了
.sign_day {
width: calc((100vw - 72px) / 4);
height: 80px;
margin-top: 8px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
color: var(--color);
}
這種方案的原理其實就是藉助了CSS的自定義變數以及CSS的作用域來實現的
所以它需要兩步:
- 自定義CSS變數(考慮作用域範圍)
- 使用CSS變數
實際上在Vue3中還有更簡便的方案!
Vue3 v-bind()
在Vue3單檔案元件的<style>
標籤支援使用v-bind
函式將 CSS 的值連結到元件中的資料。
所以以上場景還可以這樣實現:
模版:
<div :class="$style.day_item">
{{ dayItem.title }}
</div>
計算顏色值:
const color = computed(() => {
switch (props.dayItem.status) {
case 0:
return '#f8ae00'
case 1:
return '#e5353e'
case 2:
return '#1fddf4'
case 3:
return '#1ff46a'
default:
return '#191919'
}
})
style 呼叫v-bind()
使用setup中的變數
<style lang="scss" module>
.day_item {
color: v-bind(color);
}
</style>
從該圖我們可以發現Vue3中的v-bind()
原理與上面的CSS變數的原理一樣,都是藉助了CSS的自定義變數以及CSS的作用域來實現的
只不過不同的是v-bind()
生成的CSS變數前面多了一串hash
Vue3是如何編譯v-bind()的?
猜測流程
我們可以從編譯結果來進行反推
首先是我們的JS部分,編譯成了以下內容:
這裡會比沒使用v-bind()
的元件多出一個_useCssVars()
函式
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
能不能猜到這個函式的作用是什麼?如果不能,接著看下面一張圖👇
這張圖是元件的style
部分編譯之後的產物,可以看到
.day_item {
color: v-bind(color);
}
編譯成了
"._day_item_1oe25_1 {\n color: var(--5d92a9f9-color);\n}"
也就是說我們使用的v-bind
最終也是編譯成了原生CSS中var
函式,原理也是使用CSS的自定義變數
但是這裡只有使用,並沒看到css變數定義
的地方🤔,現在能夠猜測到_useCssVars()
函式的作用是什麼嗎?大機率就是用來生成css自定義變數
了。
接下來我們可以到原始碼中進行驗證:
原始碼驗證
- 找到原始碼中的
doCompileStyle
函式,打上斷點,然後就可以啟動debug
模式了
- 接著往下走你會看到一個
shortId
變數,它此時的值是什麼呢?
是不是有點眼熟,沒錯它就是後面會出現在CSS變數前面的那一串hash
- 再接著往下走,我們可以看到
postcss
外掛中新增了一個cssVarsPlugin
外掛
這個外掛的作用大家是不是已經猜到是幹嘛的了,接著往下走
- 在
cssVarsPlugin
這個方法中再加一個斷點
可以看到此時進來的decl
引數是:color: v-bind(color)
熟悉postcss
的同學應該能知道decl
是什麼意思,它表示的是css轉化為AST
後的一個節點型別
const vBindRE = /v-bind\s*\(/g;
將CSS宣告中的屬性值v-bind(color) 經過vBindRE
正則進行檢測是否為v-bind()
語句
再往下,這裡就是v-bind()
語句編譯的核心程式碼了
首先是提取變數名
這裡可以看到,執行後的結果是'color'
,也就是v-bind()
括號中的這個變數了
再往下
此時就能看到整個編譯結果了:v-bind(color)
---> var(--5d92a9f9-color)
可以看到v-bind()
的編譯其實就是透過正則處理重新生成字串
現在知道v-bind()
是如何編譯的,剩下一個重點就是:Vue是如何把style
中使用的變數轉換成CSS變數
並設定在對應dom節點上的
這個突破點在我們上面猜測流程的第一張圖,裡面有這樣一段程式碼:
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
很明顯,它就是用來生成CSS變數
- 接下來我們可以在原始碼中找到這個函式,並打上斷點
在原始碼中搜尋_useCssVars
,你會發現什麼也搜不到,這時我們可以嘗試去掉_
仔進行搜尋,你會發現有這樣一段程式碼:
const CSS_VARS_HELPER = `useCssVars`;
很明顯,後面在原始碼中我們只需要搜尋CSS_VARS_HELPER
就可以,找到以下程式碼,打上斷點,重新整理頁面
我們會發現這一段其實就是生成了我們上面那一段程式碼:
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
走到這裡你會發現好像走不下去了,沒有下一步了,因為最終我們看到的編譯後的程式碼就是這個,具體是怎麼把style
中使用的變數轉換成CSS變數
並設定在對應dom節點上的這個並不是在編譯時處理的。
想搞清楚這個我們還得在執行時打斷點除錯(這裡換成了火狐瀏覽器進行斷點除錯,不要問為什麼,問就是斷點除錯比谷歌好用)
接著往下走,會來到setVars
方法這裡
從方法名我們一眼就能看出它就是用來設定CSS變數的!
再往下走setVars
-> setVarsOnVNode
-> setVarsOnNode
在這裡最終會呼叫setProperty
方法來設定css變數。
到這裡整個流程就結束了!