css

css module 与 vue 中的 scoped css

Posted by Lauginwing on March 27, 2020

最近在使用 taro 开发多端应用,使用了 css module 处理 css 的作用域。之前一直是使用 vue 开发的,相应的也是使用 vue 内置的 css scoped 处理。下面对两种方式做一个小小的总结~

实现原理

vue scoped css(vue 项目)

1
2
3
4
5
6
7
8
9
10
11
12
// test.vue
<div class='test'>哈哈</div>
<style scoped>
.test {
  color: green;
  /deep/ {
      .box {
          color: red;
      }
  }
}
</style>

相当于

1
2
3
4
5
6
7
8
9
10
11
<style>
.test[data-v-d372c4ba] {
  color: green;
}

.test[data-v-d372c4ba] .box{
  color: red;
}
</style>

<div class='test' data-v-d372c4ba>哈哈</div>

其原理是给组件内的所有元素添加同一个全局唯一的属性:data-v-[hash],组件的 css 内的每个 class 末尾也加上对应的选择器

css module(react 项目)

1
2
3
4
5
6
7
8
9
10
// style.module.css
.test {
    color: red;
}

// test.tsx
import style from 'style.module.css';

<div className={style.test}>哈哈</div>

相当于

1
2
3
4
5
6
7
8
<style>
    .style-module__test__af14f {
        color: red;
    }
<style>

<div class="style-module__test__af14f"></div>

其原理是给元素设置全局唯一的 class:[fileName]__[className]__[hash],具体的命名规则可以在 css-loader 的配置中设置

如何覆盖子组件的样式?

vue scoped css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// parent.scss
.parent-class::v-deep {
  .child-class {
  }
}

 .parent-class {
  >>> {
    .child-class {
    }
  }
}

 .parent-class {
  /deep/ {
    .child-class {
    }
  }
}

相当于

1
2
.parent-class[data-v-asdf2] .child-class {
}

其原理是,在编译过程中,遇到 ::v-deep、»>、/deep/ 这三种伪元素或选择器时,会停止在 class 末尾添加‘[data-v-asdf]’,所以这个 class 就能作用到子组件里的元素了

css module

由于 css module 处理 css 作用域的方式有所不同,是通过将 class 名称唯一化达到目的的,而我们在写代码时是无法知道编译后的 class 名称的。所以只能通过在子组件的特定元素上添加特有属性等方式,让我们在写父组件的样式时,能定位到子组件的特定元素~, 例如现在要在父组件修改子组件中‘哈哈’的颜色为绿色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// parent.tsx
import style from "parent.module.scss";
import Child from "child.tsx";

<div className={style.box}>
  <Child />
</div>;

// parent.module.scss
.box {
    color: red;

    .text[data-role] {
        color: green;
    }
}

// child.tsx
import style from "child.module.scss";

<div className={style.box}>
    <div className={style.text} data-role>哈哈<div>
</div>;

或者在全局作用域下声明 class,前提是子组件‘哈哈’所在的元素的 class 没有使用 css module 的 class:

1
2
3
4
5
6
7
8
9
10
// parent.module.scss
:global(.text) {
  color: green;
}

 :global() {
  .text {
    color: green;
  }
}

全局作用域下声明的 class 编译时不会加上 hash。这样就可以覆盖子元素中 class 为 text 的元素了

其实,为了代码的可维护性,除了在“迫不得已”的情况下,我们不应该在父组件的 css 中修改子组件的样式,而是通过传参的方式,将样式传递给子组件使用。另外,在编写一些与业务无关的组件,如 button、popup 等组件时,尽量不要使用 scoped css、css module 等方式管理 css 作用域,而是使用一套严格规范的 class 命名规则来约束,因为这些组件被修改样式的频率会大很多,而且很可能会被抽出来单独一个库。如果使用了 css module 来管理 css 作用域,使用者修改了的样式,很可能在你更新了组件库之后就失效了~

css module 与 js

使用 css module 时,我们在 js 中引入的,实际上是一个包含着 class 编译前和编译后名称映射的对象,如:

1
2
3
4
5
6
7
// test.module.scss
.text {
  color: green;
}
.title {
  font-size: 16px;
}
1
2
3
4
5
6
7
// test.ts
import style from "test.module.scss";
console.log(style);
//输出 {
//     text: 'chiid__text__safsf1',
//     title: 'chiid__title__1wfqwf'
// }

此外,我们也可以在 css 中指定要导出的内容。比较常用的就是导出在 scss 中定义的变量:

1
2
3
4
5
6
// test.module.scss
$text-color: #222;

:export {
  textColor: $text-color;
}
1
2
3
4
// test.ts
import style from "test.module.scss";

console.log(style.textColor); // 输出 ‘#222’