不只懂 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


<<:  爬虫怎麽爬 从零开始的爬虫自学 DAY3 开发环境-2 安装Visual Studio Code

>>:  Day 3 | 游戏故事与世界观

里氏替换原则 Liskov Substitution Principle

今天来谈谈 SOLID 当中的里氏替换原则,同样的先来看一下例子。 延续先前的例子,公司持续拓展,满...

Day25 javascript 测试jQuery

今天我们来测试看看 JavaScript 框架库 – jQuery,当我们引用 jQuery如果需要...

AI ninja project [day 24] 决策树森林 --排名资料

随机决策树为随机生成许多决策树, 利用取袋法来取出选中的决策树, 而每棵树的都具有执行结果, 每棵树...

Day 28:开始来学资料系结:使用目前所学,来个简单实作吧!(二)

前一篇,我们完成了需求一: 当使用者在关键字搜寻这个 input 输入文字时,要在输入框的正下方显示...

Day06 - 端到端(end-to-end)语音辨识-CTC part 2

前一天提到说 CTC 提出了一个新的概念: 空白(blank),但在最开始的 CTC 设计中是没有使...