1. 程式人生 > 實用技巧 >Vue中scoped屬性淺析

Vue中scoped屬性淺析

Scoped CSS(Vue Loader)

在vue單檔案元件中,為了防止全域性同css類名名樣式的汙染,vue-loade對單檔案元件 <style> 標籤增加了scoped屬性的處理。原理就是在html標籤上新增data-v-xxxxxxxx屬性,然後在css類名後新增屬性選擇器,即利用css類選擇 + 屬性選擇器實現樣式區域性化:

Parent.vue

<template>
  <div class="parent">
    我是來自父元件的
  </div>
</template>

<style lang="scss" scoped>
  .parent {
    color: #333;
  }
</style>

轉換結果:

<template>
  <div data-v-2f3286d4 class="parent">
    我是來自父元件的
  </div>
</template>

<style>
  .parent[data-v-2f3286d4] {
    color: #333;
  }
</style>

使用 scoped 後,父元件的樣式將不會滲透到子元件中。不過一個子元件的根節點會同時受其父元件的 scoped CSS 和子元件的 scoped CSS 的影響。這樣設計是為了讓父元件可以從佈局的角度出發,調整其子元件根元素的樣式。

我們將Parent.vue修改為:

Parent.vue

<template>
  <div class="parent">
    <Child></Child>
    我是來自父元件的
  </div>
</template>

<script>
  import Child from './Child'

  export default {
    name: 'Parent',
    components: {
      Child
    }
  }
</script>

新增Child.vue

<template>
  <div class="child">
    <div>
      我是子元件預設色的
    </div>

    <div class="red">
      我是子元件紅色的
    </div>

    <!--<div data-v-2f3286d4 class="red">-->
      <!--我是子元件被父元件編譯過的紅色的-->
    <!--</div>-->
  </div>
</template>

<script>
  export default {
    name: 'Child'
  }
</script>

<style lang="scss">
  .child {
    color: #999;

    .red {
      color: red;
    }
  }
</style>

下面看下如何在父元件中修改子元件中樣式(兩種情況):

  1. 父有scoped,子無scoped,這種情況也是常見的各種ui中的實現,每個ui元件中無scoped,我們在父元件中可以覆蓋每個ui元件的預設樣式。如上邊提到的父元件加scoped後,在子元件的根節點會加入data-v-2f3286d4,我們在父元件直接這樣寫是沒用的:
<style lang="scss" scoped>
  .parent {
    color: #333;
    .red {
      color: greenyellow;
    }
  }
</style>

效果:

以上直接修改的話,會被編譯為:

<style lang="scss" scoped>
  .parent .red[data-v-2f3286d4] {
      color: greenyellow;
  }
</style>

子元件中red選擇的標籤是沒有 data-v-2f3286d4 屬性的,但是我們可以在子元件中開啟註釋測試下:

<div data-v-2f3286d4 class="red">
  我是子元件被父元件編譯過的紅色的
</div>

效果如下:

我們需要加 /deep/ 或者 >>> 或者 ::v-deep 來修改子元件中 .red 的樣式(會在 deep 使用後的class編譯為 `[data-v-xxxxxxxx] .red形式),我們繼續對之前開啟註釋的子元件進行註釋,並修改父元件為:

<style lang="scss" scoped>
  .parent {
    color: #333;
    /deep/ .red {
      color: greenyellow;
    }
  }
</style>

上邊會被編譯為

.parent[data-v-2f3286d4] .red {
    color: greenyellow;
}

效果:

  1. 父有scoped,子有scoped,這種情況下大多出現在我們自己的公共元件中,這種方式並不推薦,我們寫的公共元件應該不含有 scoped。參照 1 中提到的穿透元件寫法我們出現的結果如下:

出現這種問題的原因就是屬性選擇器權重 > class選擇器權重,解決的方法就是需要提高父元件中覆蓋樣式的權重,方法很簡單:加 !important。。。

<style lang="scss" scoped>
  .parent {
    color: #333;
    /deep/ .red {
      color: greenyellow !important;
    }
  }
</style>

效果:

data-v-xxxxxxxx生成規則

我們看到標籤上新增屬性,可能有些小夥伴會好奇data-v-xxxxxxxx中的xxxxxxxx是如何生成的,我查閱vue loader的倉庫中搜索發現:是否生產環境 ? hash(元件的內容) : hash(元件的相對路徑):

const moduleId = 'data-v-' + hash(isProduction ? content : shortFilePath)****

後來有大佬發現,只根據內容有可能會是生成hash值相同,比如以下方式宣告元件, issue地址

<style lang="sass" src="./index.sass" scoped></style>
<script lang="ts" src="./index.ts"></script>
<template lang="pug" src="./index.pug"></template>

後來就改為 內容 + 相對路徑 生成hash, commit地址:

const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath)

scoped總結

添加了屬性選擇器,對於CSS選擇器的權重加重了
即使加入屬性選擇器,但是clsss還是沒變,根據權重 ! important > 屬性選擇器,我們在父元件沒有加scoped時,還是有可能出現覆蓋:

<style lang="scss">
  .parent {
    color: #333;
    .red {
      color: greenyellow !important;
    }
  }
</style>

效果:

推薦使用 CSS Modules,我們直接生成一個唯一的class名,既保證了class的全域性唯一(無法造成class名樣式汙染),有沒有提高class選擇器權重的增加

參考:

你知道style加scoped屬性的用途和原理嗎?
Scoped CSS
CSS Modules
New scoped ID generation since v13.4 may cause duplicated ID (needs option to disable)