[VueJS] Style scoped 的問題與 Hack

身為 Vue (X) 眼藥水 (O) 的初學者,偶爾送送 PR 也是很合理的。


Vue-loader 提供了樣式、樣版的組合功能,基本上沒什麼好詬病的地方。但是,科技始終來自於惰性(無誤,所以,有些地方還是覺得不太夠用。

Why scoped

通常我們自己撰寫元件時,有的時候不希望樣式放在全域中被使用,只希望在這個元件(或是其子元件內發生,這個時候,就會在樣式加入 scoped 來確保樣式只會在子元件被渲染。

好處是樣式維護上比較不會那麼複雜,也不會發生樣式互相蓋版的問題。

壞處是,樣式好像比較不好維護(欸

Style scoped 問題

通常我們寫一個 Vue 會用到一些子元件,例如這樣,

<template>
  <div class="parent-component">
    <h1>我是老爸</h1>
    <child-component></child-component>
  </div>
</template>

<style scoped>
// 我是老爸,我兒子的 h1 要紅色
.parent-component .child-component h1 {
  color: red;
}
</style>

子元件例如這樣,

<template>
  <div class="child-component">
    <h1>我是兒子</h1>
  </div>
</template>

<style>
// 我是兒子,我的 h1 是藍色
.child-component h1 {
  color: blue;
}
</style>

然後,你會發現,老爸這樣的寫法,無法干涉兒子的 h1 的顏色,基於 vue-loader 當中 style-rewrite 的作法,老爸 最終樣式輸出的結果會是,

.parent-component .child-component h1[_v-d5346ea8] {
  color: red;
}

兒子 的樣式輸出結果會是,

.child-component h1 {
  color: blue;
}

根據 CSS 選擇器原則,老爸 的規則在 兒子 的 DOM Tree 當中並不符合規則,所以無論 老爸 怎麼改,基本上對於 兒子 的樣式是無法改變的。

Style scoped Hack

所以,我們換個方式想,只要老爸的樣式規則,符合兒子的 DOM Tree 即可,所以,理想的狀態下,我們只要這樣輸出,就能干涉 兒子 的顯示樣式,

.parent-component[_v-d5346ea8] .child-component h1 {
  color: red;
}

所以,只需要把 scope id 寫在 老爸 的樣式後方,而不是統一加在所有選擇器的最後面,就能解決這樣的問題。

所以我送了一個 PR 給 vue-loader

https://github.com/vuejs/vue-loader/pull/323

基本上的寫法就是,

<style scoped>
// 我是老爸,我兒子的 h1 要紅色,兒子就要是紅色
.parent-component[scoped] .child-component h1 {
  color: red;
}

div[scoped] .images[scoped] {
  // 只要加上 scoped 就會被替換成正確的 scope id
  max-width: 100%;
}
</style>

只要在任何一個選擇器後方,加上 [scoped] 這樣的屬性選擇器,就能渲染出符合我們需要的樣式規則,

.parent-component[_v-d5346ea8] .child-component h1 {
  color: red;
}
div[_v-d5346ea8] .images[_v-d5346ea8] {
  max-width: 100%;
}

這樣一來,只要 CSS 權重 正確,就能夠覆蓋子元件的樣式表,對於使用了第三方元件的人來說,永遠只能使用 Global CSS 來改寫樣式這樣的情況應該可以完全消失。

只要計算 DOM Tree 跟 CSS 權重,就能對第三方元件的樣式進行覆蓋,即便你的元件使用了 scoped 屬性,也依然能正常覆蓋。

小結

這需求只是自己爽所以改了一下(欸

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei