iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 8

不只懂 Vue 語法:請說明 style 裏的 scoped、deep selector 的作用?

問題回答

scoped 屬性的作用是避免父元件的 CSS 樣式會污染到子元件的 CSS 樣式。Deep selector 的作用是相反,即使在父元件設定了 scoped css,仍然容許讓父元件的 CSS 樣式穿透到子元件的 CSS 樣式。

以下會詳細討論答案的內容。

CSS scoped 是什麼?

.vue 檔案裏,在 style 裏加上 scoped 屬性的作用是避免目前元件的 style 會污染到子元件的 style。開發時,大多情況都建議加上 scoped

scoped 的效果會經由 vue-loader 來處理。vue-loader 是一個 Webpack 的 loader,它負責解析文件,如有需要會再引用其他 loader 來解析文件內容。最後把所有解析好的資源輸出為一個 ES Module,並預設以物件型別滙出。

回到重點,要實現 scoped,我們只需在 style 裏加上 scoped

<style lang="scss" scoped>
</style>

舉例說,現在有一個 Home 元件,裏面再包一個 Message 元件。

我們在 Home 元件裏的 style 使用 scoped,並且設定 span 的顏色為紅色。結果是只在 Home 裏的 span 會變成紅色。

Home:

<template>
    <div>
        <span> 首頁的文字 </span>
        <Message />
    </div>
</template>

<script>
    import Message from "@/components/Message";

    export default {
      components: {
        Message,
      },
    };
</script>


<style lang="scss" scoped>
    span {
      color: red;
    }
</style>

Message:

<template>
  <div>
    <span>
      Message 外層文字
      <span> Message 內層文字 </span>
    </span>
  </div>
</template>

<script>
export default {};
</script>

<style lang="scss">
</style>

結果:

看看目前的 HTML 結構:

Vue loader 編釋後,加上 data attribute 來指定只有 Home 元件才會套上 color: red; 這個樣式。因此只有「首頁的文字」才會變成紅色。

即使父元件加了scoped,子元件的根部也會受影響?

在父元件加上 scoped,看似就不會污染到子元件。但其實會污染到子元件的根部。

重用以上例子,如果把 Message 裏最外層的 div 刪走,結果是「外層 Message」和「內層 Message」都會受影響。以下示範把 Message 最外層的 div 拿走:

<template>
    <span>
      Message 外層文字
      <span> Message 內層文字 </span>
    </span>
</template>

<script>
export default {};
</script>

<style lang="scss">
</style>

結果:

因為沒有了 div,而 span 是行內元素,所以這三段文字都排在同一行。而重點是,現在 Message 元件裏的所有文字都變成紅色。

HTML 結構:

在前一個示範,本來 data-fae5bece 這個屬性是套用在 div 上,現在因為把 div 刪掉,所以直接套在 span 上,以致整個 Message 元件裏的 span 都是紅色。

這就是官方文件提及的意思:

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

對於子元件根部的 CSS,父元件 CSS 會優先於子元件的 CSS

重用以上例子,如果在 Message 加上 scoped,並設定 span { color: blue; }。那麼目前 Message 元件就會同時受父元件和它自己的 scoped 影響,換言之,它會有兩個 data attribute。

以下示範在 Message 元件加上它自己的 span 樣式,顏色是藍色。

Message.vue:

<template>
    <span> Message 外層文字
        <span> Message 內層文字 </span>
    </span> 
</template>

<script>
    export default {}
</script>

<style lang="scss" scoped>
    span {
         color: blue;
    }
</style>  

結果:

HTML 結構:

最外層的 span 被加上兩個 data attribute,但 Home 的 data attribute 被劃掉,因此可見父元件的 scoped CSS 是優先於子元件的 scoped CSS。

完整程式碼

https://codesandbox.io/s/scoped-css-lbc9z

什麼是 deep selector?

deep selector 就是做相反的事。意思是無視 scoped 的限制,即使設定了 scoped,還是可以把父元件的樣式穿透到子元件。

deep selector 的寫法有幾種:

.home >>> .message {
    color: red;
}
.home:deep .message {
    color: red;
}
.home /deep/ .message {
    color: red;
}
.home::v-deep .message {
    color: red;
}

另外也可以把前面的父元件省略:

:deep .message {
    color: red;
}
/deep/ .message {
    color: red;
}
::v-deep .message {
    color: red;
}

注意:

  • 如果你是使用 sass 或 scss, >>> 不能被編釋。
  • vue/compiler-sfc 建議使用 :deep 代替 ::v-deep

修改之前的例子,在 Home 最外層加上 home class,在 Message 的外層加上 message class。最後,在 Home.vue 檔案使用 deep selector:

Home.vue:

<template>
  <div class="home">
    ...
  </div>
</template>

<script>
    ...
</script>

<style scoped lang="scss">

  span {
    color: red;
  }

  .home:deep .message {
    color: red;  
  }
</style>

Message.vue:

<template>
    <div class="message">
        ...
    </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped></style>  

結果:

HTML 結構:

因為我的例子是使用 scss 來寫 CSS,所以不能使用 >>> 的寫法。另外,如果我們在子元件設定 span 樣式,像以下做法:

Message.vue:

<template>
    <div class="message">
        <span> Message 外層文字
            <span> Message 內層文字 </span>
        </span>
    </div>
</template>
<script>

export default {
}
</script>
<style lang="scss" scoped>
 span {
     color: blue;
 }
</style>  

結果是子元件的文字會變成藍色,優先於父元件設定的紅色:

HTML 結構:

完整程式碼(子元件裏沒有設定樣式)

https://codesandbox.io/s/scoped-css-deep-selector-k9111?file=/src/components/Message.vue

其他應用情況:修改第三方套件的樣式

當我們以元件的方式來載入第三方套件,並且想修改它的樣式,就有機會要用 deep selector。例如我曾經在專案中以元件方式載入 CKEditor,但找不到在哪裏可以修改 CKEditor 輸入框的高度,於是我使用 deep selector 的方式來處理:

引入 ckeditor 元件來載入 CKEditor:

<ckeditor v-model="editorData" :editor="editor" :config="editorConfig"></ckeditor>

我當時在 style ,針對 CKEditor 裏某個 class 的設定:

:deep .ck-editor__editable {
  height: 400px;
}

總結

  • 大多情況都建議使用 scoped CSS。
  • scoped 屬性的作用是避免父元件的 CSS 樣式會污染到子元件的 CSS 樣式。
  • Vue 是透過 vue loader 來編釋 .vue 檔案,以及透過 data attribute 的方法,實現 scoped CSS。
  • 使用 deep selector 可以把父元件的 CSS 樣式穿透到子元件的 CSS 樣式,即使父元件的 CSS 設定了 scoped。
  • deep selector 的語法有幾種:>>>::v-deep (不建議使用)、 :deep/deep/。 但sass 或 scss 無法編釋 >>>

參考資料

/deep/ 是什麼? — 聊聊 Vue 裡的 scoped css
Vue Loader - Deep Selectors


上一篇
不只懂 Vue 語法:什麼是 Virtual DOM?Vue 如何利用 Virtual DOM?
下一篇
不只懂 Vue 語法:為何元件裏的 data 必須是函式?建立 data 時能否使用箭頭函式?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31

尚未有邦友留言

立即登入留言