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還是沒變,如果全局還存在同class名稱的樣式,還是有可能出現覆蓋,比如用在 App.vue 中也定義了 .red 樣式:

效果:

推薦使用 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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM