AnnGreen1 / article

0 stars 0 forks source link

CSS BEM & Vue Scoped & Vue CSS Module 食用指南 #8

Open AnnGreen1 opened 4 months ago

AnnGreen1 commented 4 months ago

昨天在群里看到大家对CSS BEM和CSSmodule的讨论,激起了我的好奇心,我也想通过这篇文章向更多人科普一下CSS BEM / CSS Module / scoped 在代码中到底是个啥,他们究竟在做些什么。

方便起见,Scoped和CSS module的内容我主要使用vue作为示例,因为vue cli创建的工程是默认支持以上两种情况的。

为什么会出现这么多名词

首先我们需要了解这句话 ------ CSS的影响是全局的。

但是更多时候我们更喜欢他局部生效,所以就出现了上面的几种解决方案,下面是对此的详解。

CSS BEM

更多详情参考:CSS BEM 书写规范

<style>
  .模块名-元素名--修饰符{}
  .moduleName-elementName--modify {}
  .paging-button--prev {}
  .paging-button--next {}
</style>

如上所示,CSS BEM (Block - Element - Modify)其实是一种约定俗成的 模块-标签--修饰符 的 CSS 命名方式。

举例:在分页组件(/app/components/paging.vue)中,模块名就是paging模块内的每一个元素的class命名都必须是模块名开头,上一页按钮对应的元素名或者内容(可以自己取一个合适的名字)为 button,最终他的class就为 paging-button--prev

优点:

  1. 一种CSS局部样式的实现方案;
  2. 可以很清楚的知道样式来源哪个组件;
  3. CSS基本都为平级,易于覆盖和修改;
  4. 可以尽可能地避免子代元素名称重复,导致的渲染错误;

缺点:

  1. 编码麻烦,而且会导致代码量增加,每一个class都增加了长度;

Scoped

我们看到的scoped通常是如下使用的:

<template> <button class=”button” /> </template>

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

最后的渲染实际是这样的:

<style>
  .button[data-v-f61kqi1] { color: red; }
</style>

<button class="button" data-v-f61kqi1></button>

scoped实际上是给每个标签加入了一个data属性,每个scoped里的样式渲染的时候,是根据这个标记的data属性渲染,从而实现的局部样式。

穿透scoped

场景 参考文章:《Vue中的scope样式穿透(转载)》

但是在某些场景下,scoped是不够用的。

比如我们的页面嵌套了第三方的一个组件,我们在css上定位到组件需要修改的class为a,scoped规定我们是不能修改这个class的,这个时候就需要穿透scoped,这种情况可以使用 >>> 连接符(或者 /deep/ )实现。

<style scoped>
    外层 >>> 第三方组件 {
        样式
    }
    例如,
    .container >>> .a {
        color: 'red'
    }
</style>

优点:

  1. 一种CSS局部样式的实现方案;
  2. 使用简单;
  3. CSS基本都为平级,易于覆盖和修改;

缺点:

  1. 如果父组件和子组件出现了同名的class,父组件的样式会泄露到子组件上;

image

CSS Module

如何在vue项目里开启 CSS Module,请移步官网:https://vue-loader.vuejs.org/zh/guide/css-modules.html#%E7%94%A8%E6%B3%95

参考文章:《[译] Vue: scoped 样式与 CSS Module 对比》 更多CSS Module技巧,请参考:《CSS Modules 用法教程》

我平时没有用过CSS Module,但是经过这一次的了解,我非常期待在项目中使用它,他的使用如下:

<style module>
  .button { color: red }
</style>

乍一看,他在vue中的使用几乎和scoped没有差别,但他非常不同。

  1. CSS Module生成的CSS如下,类似BEM规范;
    <style>
    .ComponentName__button__2Kxy {
    color: red;
    }
    </style>
  2. 他和scope的不同之处,在于页面对于class的引用需要通过 $style 来获取;
    
    <template>
    <button :class="$style.button" />
    </template>
以上生成如下代码: 
```html 
<style>
  .ComponentName__button__2Kxy {
    color: red;
  }
</style>

<button class=”ComponentName__button__2Kxy”></button> 

这样乍一看很麻烦,但厉害在这里,可以在 CSS 中定义你的色彩变量的同时将其导出,以供你的组件使用,从此不需要在组件里重复声明了!

<template>
  <div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>

<style module lang="scss">
  $primary-color: #B4DC47;

  :export {
    primaryColor: $primary-color
  }
</style>

修改第三方组件

CSS Modules 允许使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串。

其他文章参考:使用了css modules怎么修改antd组件的默认样式,修改antd的样式

如果想修改全局的第三方样式:

:global{
    .ant-modal-content{
        background-color: transparent;
    }
}

如果只想修改制定的第三方样式,那就给模块新增一个class,再修改:

<Modal className="tableBottomPop"></Modal>

:global{
    .tableBottomPop{  
        .ant-modal-content{
            background-color: transparent;
        }
    }
}

优点:

  1. 不需要写BEM,但是会生成类似BEM规范的class样式,不会给css增加额外的权重;
  2. 可以很清楚的知道样式来自哪个组件,同时可以显示地分辨这个样式是组件自身的还是继承父组件的;
  3. 可以复用css中定义的变量;

缺点:

  1. 使用稍许复杂,需要通过$style来对每个class进行引用;

总结

以上的三种都是针对CSS局部样式的实现方案,没有太多的孰是孰非,我们根据自己的使用场景进行挑选就好。

选定某一种方案,我们多记住他的"优缺点",使用好的尽量避免差的就很可以了,实际中可没那么多的大问题"吓人",总可以找到方法解决的。

作者:vivi_chen 链接:https://juejin.cn/post/6885587863720132615 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。