共计 4030 个字符,预计需要花费 11 分钟才能阅读完成。
css_scoped 与 css_module
咱们晓得,简略的 class 名称容易造成 css 命名反复,比方你定义一个 class:
<style>
.main {float: left;}
</style>
如果他人刚好也定义了一个 className:.main
,你的 float:left
就会影响到它。
所以 Vue 中创造了css_scoped
,其原理就是在 class 名称后加上一个 data 属性选择器:
<style scoped>
.main {float: left;}
</style>
// 本义后变成
<style>
.main[data-v-49729759] {float: left}
</style>
css_scoped
是 Vue 的专用计划,如果你应用 React 等其它 UI 框架,那么你能够应用更通用的 css_module
,其原理是为款式名加hash
字符串后缀,从而保障 class 名全局惟一:
<style module>
.main {float: left;}
</style>
// 本义后变成
<style>
.main_3FI3s6uz {float: left;}
</style>
相比于 css_scoped
,css_module
计划更通用,不扭转其自身的权重,而且渲染性能要比前者好很多,所以更举荐大家应用css_module
。
不足之处
然而不论是 css_scoped
还是css_module
,都绕不开 2 大毛病:
- 因为加上了随机字符,所以如果想在父组件中笼罩子组件中的款式变得麻烦,尽管
css_scoped
能够应用穿透,但这样容易引发别的问题。 - 加上随机字符让 class 名称变得不优雅,也影响编译速度。
css 命名空间
咱们来回顾一下,在 css_scoped
和css_module
呈现之前,人们是如何防止 css 命名抵触的?
对,就是人为的定义一些css 命名空间
。
那个时候,对每个 Component 组件都会在其根节点上定义一个不反复的 ID 或者 class 作为其 命名空间,而后其外部的其它 class 都会以此命名空间作为前置限定,比方:
<div class="table-list">
<div class="hd"></div>
<div class="bd"></div>
<div class="ft"></div>
</div>
<style>
.table-list {
> .hd {color: red}
> .bd {color: blue}
}
</style>
这样一来,只有保障根节点的 class 不反复,其子节点的 class 就不会反复。
而对于一些全局款式,人们习惯加上一个 g-
作为命名空间,比方:
<style>
.g-hd {color: red}
</style>
这种依附人为约定的 css 命名空间,尽管比拟原始,但有其长处:
- 简略无效,按
模块 - 组件名称
的命名约定,基本上很容易保障其不反复。 - 款式名更具语义,从任何一个 dom 登程,向上肯定能找到其组件根节点 class 名,基本上就能猜到其组件所在的业务模块、组件地位等。
- 父组件很容易利用权重笼罩子组件的任何款式。
css_namespace + css_module
如果咱们把 css_module
和css_命名空间
联合起来,组件的命名空间由 css_module
主动生成,那岂不是一种更优雅的解决 css 抵触的计划么?
css_module
中有 2 个特地的作用域限定符:
-
:global 该限定符下的 class 名称将放弃原样,不会被 css moudle 转换,比方:
:global {.test1 { color: blue;} .test2 {color: red;} } // 编译后 .test1 {color: blue;} .test2 {color: red;}
-
:local 该限定符下的 class 名称,将会被 css moudle 转换,比方:
:local {.test1 { color: blue;} .test2 {color: red;} } // 编译后 .test1_3zyde4l1y {color: blue;} .test2_2DHwuiHWM {color: red;}
如果咱们应用
css_namespace + css_module
:<div :class="styles.root"> <div class="hd"></div> <div class="bd"></div> <div class="ft"></div> </div> <style module> :global {:local(.root) { > .hd { color: red; .title {font-size: 18px;} } > .bd {color: blue;} } } </style> //css 编译后 <style> .root_3zyde4l1y > .hd{color: red;} .root_3zyde4l1y > .hd .title{font-size: 18px;} .root_3zyde4l1y > .bd{color: blue;} </style>
这样的意思是:
- 每个组件原则上仅 根节点 应用
css_module
主动生成不反复的 class 名称,其余外部元素放弃原始命名,不做任何转换。(当然某些状况下,也能够应用多个转换) - 为了保障孙子辈款式不影响他人,能够适当退出 dom 层级限定,比方
> .hd
这样就只会影响子级的.hd
。
去除 css_moudle 随机字符
<style>
.root_3zyde4l1y > .hd{color: red;}
.root_3zyde4l1y > .hd .title{font-size: 18px;}
.root_3zyde4l1y > .bd{color: blue;}
</style>
根节点上的 class 命名带个hash 小尾巴
,依然很不优雅。其实 hash 字符只是为了保障这个名称全局惟一而已,你也能够应用另外的办法来保障。如果你为工程设计一个有意义的目录构造,那么齐全能够应用目录门路来代替 hash 字符串,比方你的工程目录如下:
src
├── components
│ ├── moduleA
│ │ ├── componentX
│ │ ├── componentY
│ ├── moduleB
│ │ ├── componentZ
那么:components-moduleA-componentX
这个目录门路肯定是全局惟一的,所以你能够应用这个门路来代替 hash 字符,css_module 提供了自定义转换 className 的办法:
type getLocalIdent = (
context: LoaderContext,
localIdentName: string,
localName: string
) : string;
你能够通过该办法来将目录门路映射为 class 名称,并替换掉一些固定的目录,比方工程目录如下:
src
├── assets
│ ├── css
│ ├── global.module.scss // 全局款式
│ ├── :local(.loading) {} // 全局款式只须要加个 g - 前缀,编译成.g-loading
├── components
│ ├── NavBar
│ ├── index.module.scss
│ ├── :local(.root) {} // 依据目录门路可编译成即可.comp-NavBar
│
├── modules
│ ├── user
│ ├── components
│ ├── LoginForm
│ ├── index.module.scss
│ ├── :local(.root) {} // 依据目录门路可编译成.user-LoginForm,│
留神的是src/modules/user/components/LoginForm/index.module.scss
,依据目录门路能够生成:modules-user-components-LoginForm,但因为 user 是一个 module,其名称是惟一的,且内部结构遵循约定,所以能够简化为:user-LoginForm
依据 class 名称揣测文件地位
.g-loading
– 带g-
前缀,阐明它是一个全局 class,对应的文件肯定是src/assets/css/global.module.scss
.comp-NavBar
– 带comp-
前缀,阐明它是一个公共组件,对应的组件肯定是src/components/NavBar
.user-LoginForm
– 依据约定,对应的组件肯定是src/modules/user/components/LoginForm
示例及源码
如果你也应用相似的工程目录,那么能够间接应用我封装好了的门路映射函数getCssScopedName
:
const {getCssScopedName} = require('@elux/cli-utils');
const srcPath = path.resolve(__dirname, '../src');
// webpack css-loader
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: {getLocalIdent: (context, localIdentName, localName) => {return getCssScopedName(srcPath, localName, context.resourcePath);
},
localIdentContext: srcPath,
},
},
};
当然你也可本人实现个性化的getLocalIdent
,无非就是一些正则匹配与替换罢了 …
采纳 css_namespace + css_module
的理论案例:
- 基于 Antd 的后盾管理系统
- 或者应用任意一个 elux 工程模版:
npm create elux@latest 或 yarn create elux
如图所示,通过 class 名称基本上就能揣测出组件地位 …
欢送交换,自己近期文章:
- 前端架构 - 分层而治,铁打的 MV 流水的 C
- 手撸 Router,还要啥 Router 框架?让 react-router/vue-router 躺一边凉爽去
- 不想当 Window 的 Dialog 不是一个好 Modal,弹窗翻身记