SCSS Module 这样处理配置和使用太赞了
SCSS Module 只是Scss和Css Module结合,可以利用SCSS对代码静态处理的能力,使得样式处理更强大一些,并不是什么新的东西,对比css-in-js和scoped,个人偏向喜欢Scss Module做样式隔离,先说一下优点:
- 配置灵活,可根据具体业务需要选择不同的模式
- 定制化className,更灵活,可以根据自己的需要定制每个模块className 输出
- 调试更方便,通过className可以直观的找到代码所在的位置
- 利用CSS Module的作用域可以方便对已有的样式可进行二次覆盖,达到理想效果
- 增加Scss,对样式处理更加方便
下面就从配置和作用域两方面来学会SCss Module吧,相信你也会爱上它的
配置
css module 配置都差不多,当然这里肯定也要配置scss的哈,最详细的就数Webpack中的了,今天就已webpack css-loader 中modules为例,探索一些css module的配置.这里不会全部介绍,有些配置一块就明白,也不常用的化,大家可以看文档,下面就介绍一下modules
中的配置选项:webpack 文档
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]_[local]_[hash:base64:4]'
}
}
},
mode 属性
可选值 - local
、global
、pure
和 icss
。 文档中介绍了,我们一个个来了解一下:
local
:默认值。CSS 模块会使用本地作用域策略进行处理,每个类名都会被转换为一个唯一的名称,该名称作用域限定于该模块
举个例子说明一下:
也就是css module 文件中的类名默认都使用本地的作用域:local都会被转换,带有:global 作用域的将被忽略,不做处理
配置:
modules: {
mode: 'local',
localIdentName: '[name]_[local]_[hash:base64:4]'
}
使用
:local(.item1) {
width: 500px;
height: 20px;
background-color: red;
}
.item2 {
width: 500px;
height: 20px;
background-color: yellow;
}
:global(.item3) {
width: 500px;
height: 20px;
background-color: green;
}
<div className={$style.item1}>我是item1</div>
<div className={$style.item2}>我是item2</div>
<div className={$style.item3}>我是item3</div>
生成内容
<div class="index_item1_paMR">我是item1</div>
<div class="index_item2_n99k">我是item2</div>
<div class="item3">我是item3</div>
可以看出,mode为
local
时,类名相当于是省略了:local(类名)
,:global
包裹的类名不被转换
global
:在此模式下,CSS 类名是全局的,可以在整个应用程序中使用。
举个例子说明一下:
也就是说,css 文件中的类名将默认将不做转换,带有:local 包裹的类名将被转换
配置:
modules: {
mode: 'global',
localIdentName: '[name]_[local]_[hash:base64:4]'
}
使用
:local(.item1) {
width: 500px;
height: 20px;
background-color: red;
}
.item2 {
width: 500px;
height: 20px;
background-color: yellow;
}
:global(.item3) {
width: 500px;
height: 20px;
background-color: green;
}
<div className={$style.item1}>我是item1</div>
<div className='item2'>我是item2</div>
<div className='item3'>我是item3</div>
生成内容:
<div class="index_item1_paMR">我是item1</div>
<div class="item2">我是item2</div>
<div class="item3">我是item3</div>
可以看 默认的:global(.item2) 和 .item3 是等价的,类名都不会被转换,而:local(.item1)的类名被转换了,所以使用mode:'global'
css 类名默认将不被准换,可以在整个应用中使用
pure
:在此模式下,和local类似,但选择器必须包含至少一个局部 class 或者 id
我们举个例子说明:
配置:
modules: {
mode: 'pure',
localIdentName: '[name]_[local]_[hash:base64:4]'
}
使用
.item1 {
width: 500px;
height: 20px;
background-color: red;
}
:local(.item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
<div className={$style.item1}>我是item1</div>
<div className={$style.item2}>我是item2</div>
不好意思,这样写报错了,不是说类似:local吗,为什么报错了呢":local(.item2)" is not pure (pure selectors must contain at least one local class or id)
?我们找到css-loader 后发现它引入了一个库postcss-modules-local-by-default, 具体代码在这
if (pureMode && context.hasPureGlobals) {
throw rule.error(
'Selector "' +
rule.selector +
'" is not pure ' +
"(pure selectors must contain at least one local class or id)"
);
}
ok,但要注意这里写的是local class or id
是本地作用域哦,也就是被类名被转换了的,不是:global被忽略的,也不是原生的class类名
.wrap {
:global(.item3) {
width: 500px;
height: 20px;
background-color: green;
}
:local(.item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
.item1 {
width: 500px;
height: 20px;
background-color: red;
}
}
<div className={$style.wrap}>
<div className={$style.item1}>我是item1</div>
<div className={$style.item2}>我是item2</div>
<div className='item3'>我是item3</div>
</div>
生成内容:
<div class="index_wrap_J3qp">
<div class="index_item1_paMR">我是item1</div>
<div class="index_item2_n99k">我是item2</div>
<div class="item3">我是item3</div>
</div>
icss
:CSS 模块会使用Interoperable CSS (ICSS)
规范进行处理。
先介绍一下Interoperable CSS (ICSS)
:
ICSS是标准CSS的超集,使用两个额外的伪选择器::import
和:export
即可,简单点说就是普通的css文件外加可以import和export,引入和到出变量值
配置:
modules: {
mode: 'icss',
localIdentName: '[name]_[local]_[hash:base64:4]'
}
使用
:global(.item3) {
width: 500px;
height: 20px;
background-color: green;
}
:local(.item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
.item1 {
width: 500px;
height: 20px;
background-color: red;
}
:export {
height: 50px;
}
<div
className='item1'
style={{ height: $style.height }}
>
我是item1
</div>
<div className={$style.item2}>我是item2</div>
<div className='item3'>我是item3</div>
可想而知,只有item1有效和设置的高度生效,:local和:global在这是不生效的了,我们看生成的效果
<div class="item1" style="height: 50px;">我是item1</div>
<div class="">我是item2</div>
<div class="item3">我是item3</div>
import 和 export 不适用icss 模式也是有效的,只是设置成icss模式,类名就无法转换了,只使用css的基本功能
exportLocalsConvention 属性
类型:String|Function
默认:取决于 modules.namedExport
选项值,如果为 true
则对应的是 camelCaseOnly
,反之对应的是 asIs
这个webpack文档介绍的很清楚,我这里就提一嘴,详情可以看文档
这个属性,说白话就是,我们css的类名可能是a-b
这样形式的,但你在js中这样写不太友好$style['a-b]
,使用这个属性将其转换成 $style.aB
驼峰更友好,当然具体转成什么样,看你怎么配置了
名称 | 类型 | 描述 |
---|---|---|
'asIs' | {String} | 类名将按原样导出。 |
'camelCase' | {String} | 类名将被驼峰化,原类名不会从局部环境删除 |
'camelCaseOnly' | {String} | 类名将被驼峰化,原类名从局部环境删除 |
'dashes' | {String} | 类名中只有破折号会被驼峰化 |
'dashesOnly' | {String} | 类名中破折号会被驼峰,原类名从局部环境删除 |
配置:
modules: {
exportLocalsConvention: 'camelCaseOnly',
localIdentName: '[name]_[local]_[hash:base64:4]'
}
使用
:local(.wrap_item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
.wrap_item1 {
width: 500px;
height: 20px;
background-color: red;
}
import $style from './index.scss';
const App = function () {
return (
<>
<div className={$style.wrapItem1}>我是item1</div>
<div className={$style.wrapItem2}>我是item2</div>
</>
);
};
生成内容
<div class="index_wrap_item1_azLw">我是item1</div>
<div class="index_wrap_item2__F_J">我是item2</div>
localIdentName
这个比较简单,文档都有说明,如果这里面没有我们想生成的格式,或是无法满足我们的需求,可以使用下面的getLocalIdent
来完成自定义输出类名
类型:String
默认:'[hash:base64]'
允许配置生成的本地标识符(ident)。
支持的模板字符串:
[name]
源文件名称[folder]
文件夹相对于compiler.context
或者modules.localIdentContext
配置项的相对路径。[path]
源文件相对于compiler.context
或者modules.localIdentContext
配置项的相对路径。[file]
- 文件名和路径。[ext]
- 文件拓展名。[hash]
- 字符串的哈希值。基于localIdentHashSalt
、localIdentHashFunction
、localIdentHashDigest
、localIdentHashDigestLength
、localIdentContext
、resourcePath
和exportName
生成。[<hashFunction>:hash:<hashDigest>:<hashDigestLength>]
- 带有哈希设置的哈希。[local]
- 原始类名。
getLocalIdent
你可以拿到本地类名localName
和当前文件的地址context.resourcePath
,每个参数代表什么打印一下就可以了,然后就可以根据业务需要和设计来自定义类名的输出
文档
类型:Function
默认:undefined
可以指定自定义 getLocalIdent
函数的绝对路径,以基于不同的架构生成类名。 默认情况下,我们使用内置函数来生成 classname。 如果自定义函数返回 null
或者 undefined
, 我们将降级使用内置函数来生成 classname。
如下举个例子:
modules: {
getLocalIdent: (context, localIdentName, localName, options) => {
if(/a/.test(context.resourcePath)){
return `a_${localName}`
}
if(/b/.test(context.resourcePath)){
return `b_${localName}`
}
return `${localName}`;
}
},
这些常用的CSS Module配置就介绍到这了,其它的大家可以试着看看都有什么效果。
作用域
在写的时候和写普通scss或css 没有多大的差别,但牵扯到二次封装公共组件或是修改其他三方组件代码时就需要掌握一些CSS Module的技巧了,下面就来看看CSS Module的作用域:local 和:gloabl 有什么神奇的效果。
这个跟上文说的mode 有关吗?没什么关系的哈,这里说一下,:lcoal我们称本地作用域,其实也可以说是隔离作用域,简单点说就是碰到这个类名就帮你转换,:global 称为全局作用域,碰到这个类名就不转,无论时mode是local还是global 只是说css文件中默认写的类名是local还是global的,这点从上面的例子可以看出来
下面我们采用的默认配置,也就是mode:'local'
来举例说明
作用域写法
下面这两个情况是一样的,写法上类似,没有什么区别
局部作用域
:local(.item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
:local {
.item3 {
width: 500px;
height: 20px;
background-color: green;
}
}
全局作用域
:global(.item2) {
width: 500px;
height: 20px;
background-color: yellow;
}
:global {
.item3 {
width: 500px;
height: 20px;
background-color: green;
}
}
覆盖子元素样式
既然覆盖某子元素的样式,那这个要被覆盖的类名就不能再被转换,从上文可知,我们可以使用:global 来忽略 CssModule的转换,当然写法有多种,根据自己需要自行选择,具体实现如下:
- 写法1
.wrap {
margin: 50px;
:global(.button) {
background-color: red;
}
}
- 写法2
.wrap {
margin: 50px;
:global {
.button {
background-color: red;
}
}
}
- 写法3
.wrap {
margin: 50px;
:global .button {
background-color: red;
}
}
- 写法4
也可以像下面这样写 比如
.wrap :global .button
或是.wrap :global(.button)
都可以的
.wrap {
margin: 50px;
}
.wrap :global .button {
background-color: red;
}
覆盖同级元素样式
比如说Button 并没有包在:lcoal作用域下,如下所示,可以直接修改button的样式
.button {
height: 200px;
width: 500px;
border: 1px solid yellow;
}
.button.button-color {
color: blue;
}
const App = function () {
return <Button></Button>;
};
export default App;
使用如下覆盖,同:local的写法一致,同样有这三种方式是等价的,但一般是不希望这样的,因为如果其他地方也这样修改了,可能就存在冲突,某方的样式被覆盖的情况,具体我们再细说这类情况:
:global {
.button {
background-color: red;
}
}
:global .button {
background-color: red;
}
:global(.button) {
background-color: red;
}
- 存在可以传入class的情况,比如:
.buttonWrap {
background-color: red;
color: #fff;
}
<Button className={$style.buttonWrap}></Button>;
背景可能会被覆盖,因为同一个class,它的类名之间的属性并不是后面的覆盖前面的,它们的权重是一致的,也就存在有的打包发布后,样式覆盖正常,有的显示不正常的情况了,比如说:
.a{
color:red;
}
.b{
color:#fff;
}
.c{
color:red;
}
<div class="a b c" />
这三个类,一般我们一看知道显示red,也就是后面的覆盖前面的,但我们如果把b类写再后面,这个时候就显示的是#fff,因为他们的权重是一致的,谁写在后面就显示谁,但有时候打包容易和加载的时候,你也不一定确定哪个就在前那个在后了,也就存在偶然的覆盖失效的问题了,那如何解决呢?
下面的都可以进行覆盖,写法有很多种,你也可以探索其他的写法
.buttonWrap:global(.button.button-color) {
color: #fff;
background-color: red;
}
:global :local(.buttonWrap).button.button-color {
color: #fff;
background-color: red;
}
.buttonWrap {
&:global(.button.button-color) {
color: #fff;
background-color: red;
}
}
:global {
:local(.buttonWrap).button.button-color {
color: #fff;
background-color: red;
}
}
.buttonWrap {
&:global {
&.button.button-color {
color: #fff;
background-color: red;
}
}
}
生成的样式
.index_buttonWrap_QT2R.button.button-color {
color: #fff;
background-color: red;
}
- 外界没有提供class
这个情况是很不好的,没有本地作用域的隔离,相当于普通css的覆盖,写法同上也有很多种,这里就不一一列举了,大家可以通过添加div或是父级或是属性,标签等方式进行权重的提升和隔离,这里举个例子
:global(div.button.button-color) {
color: #fff;
background-color: red;
}
比如 button组件有个type属性,就可以这样写
:global(.button.button-color)[type='button'] {
color: #fff;
background-color: red;
}
希望对大家有用,也希望大家多多支持,给作者一些动力,写下去,谢谢
转载自:https://juejin.cn/post/7310412252229435418