为什么选择了 css-module 而不是 scoped ?
在 vue3/vue2 开发中,为解决 css 样式冲突和全局污染的问题,通常有如下方案:
- 通过命名空间(前缀区分):如使用
bem规范 - 使用组件作用域
Scoped CSS - 使用
CSS Modules
第一种方案 更推荐用于公共组件,生成的类名 可读性高 且 方便样式覆盖。
但对于绝大部分的业务组件,使用 局部作用域 方案可以减少思考 class 命名带来的心智负担,更关注于业务实现。常见的有 第二种方案 和 第三种方案。
下面以类名 .red 举例这两种方案的区别:
scoped
通过给元素添加自定义属性如 data-v-hash,并在样式中给类名添加上属性选择器,使该组合”唯一“(实际只是加上了属性选择器的权重,当遇到外部同类名或权重更高的类名时仍然会被覆盖或叠加样式!)。
如图: class="red" 没变,多了自定义属性 data-v-7a7a37b1,且样式中类名多了属性选择器 .red[data-v-7a7a37b1] {}
scoped 方案可能出现的样式覆盖问题
实际开发中可能会遇到下面问题的,常常是 .sidebar .menu .list .item .row ... 这类通用名称的类名。
当有以下这些场景之一:
- 公共样式中有
.red { background: red; } - 没有使用
scoped的组件被加载,且有.red { background: red; } - 父组件用了
scoped且有.red { background: red; },子组件用了scoped且根节点也有 red 类名和对应样式 - ...
header元素会被加上红色背景,但有时候我们希望使用了 scoped 的组件内的 red类 的样式按我们期望的来,不用去做过多的样式重写或提高权重操作,如图:
又或者当以上场景有更高权重的样式时,如 #app .red { color: darkred; } ,会直接将局部样式字体红色改为深红色。有时候已经开发好的界面可能会因为后加的样式导致样式出错需要返工。如图:
css-module
编译时通过将 类名 改写为 类名+hash 的组合,让这个类名在全局中 独一无二 ,能确保不会被其他地方的样式覆盖。
如图: :class="$style.red" 被转换为 class="_red_zkzc1_7"
即使全局公共样式有更高权重的样式 #app .red { background: red; } ,也不会影响到该样式。
如果希望公共样式也起作用(主观上希望使用到该公共样式),则可这么写:class="red" :class="$style.red" 或 :class="['red', $style.red]",这样两个样式都会生效且能确保局部的样式不会被其他地方的样式不小心影响到。
tip: 如果你觉得使用
css-module的写法太繁琐了,那可以安装 vite-plugin-vue-css-module 这个插件,它提供的语法糖让你写类名跟之前一样简单!
为什么选择 css-module
- 用
css-module比scoped写起样式来更安全省心:css-module方案生成了唯一类名,不会被编译前的同类名样式影响,可避免被其他地方的样式覆盖或叠加。而scoped方案存在着可能会被其他地方的样式覆盖或者叠加的可能,当项目越大,样式越多时,越容易出现出乎意料的样式问题。 css-module比scoped效率略高:css-module类名唯一,浏览器查找性能高。scoped通过唯一的属性选择器查找,性能略低。但这种差异通常是微小的。- 当希望在样式局部作用的组件中使用全局样式时,使用
css-module+ 公共类名 更能直接表达你的主观意愿,且可维护性更好,写的更放心。