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_scopedcss_module方案更通用,不改变其本身的权重,而且渲染性能要比前者好很多,所以更推荐大家使用css_module

不足之处

然而不管是css_scoped还是css_module,都绕不开2大缺点:

  1. 由于加上了随机字符,所以如果想在父组件中覆盖子组件中的样式变得麻烦,虽然css_scoped可以使用穿透,但这样容易引发别的问题。
  2. 加上随机字符让class名称变得不优雅,也影响编译速度。

css命名空间

我们来回忆一下,在css_scopedcss_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_modulecss_命名空间结合起来,组件的命名空间由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的实际案例:

如图所示,通过class名称基本上就能推测出组件位置...

一种比css_scoped和css_module更优雅的避免css命名冲突小妙招的更多相关文章

  1. 更好用的css命名方式——BEM命名

    一.什么是BEM? BEM代表块(Block),元素(Element),修饰符(Modifier).无论是什么网站页面,都可以拆解成这三部分. 二.带你认识网页 我们来看一下qq的官网,它可以由三个块 ...

  2. PostCSS一种更优雅、更简单的书写CSS方式

    Sass团队创建了Compass大大提升CSSer的工作效率,你无需考虑各种浏览器前缀兼,只需要按官方文档的书写方式去写,会得到加上浏览器前缀的代码,如下: .row { @include displ ...

  3. 少年,是时候换种更优雅的方式部署你的php代码了

    让我们来回忆下上次你是怎么发布你的代码的: 1. 先把线上的代码用ftp备份下来 2. 上传修改了的文件 3. 测试一下功能是否正常 4. 网站500了,赶紧用备份替换回去 5. 替换错了/替换漏了 ...

  4. C#中一种替换switch语句更优雅的写法

    今天在项目中遇到了使用switch语句判断条件,但问题是条件比较多,大概有几十个条件,满屏幕的case判断,是否有更优雅的写法替代switch语句呢? 假设有这样的一个场景:商场经常会根据情况采取不同 ...

  5. 使用 Promises 编写更优雅的 JavaScript 代码

    你可能已经无意中听说过 Promises,很多人都在讨论它,使用它,但你不知道为什么它们如此特别.难道你不能使用回调么?有什么了特别的?在本文中,我们一起来看看 Promises 是什么以及如何使用它 ...

  6. 使用Castle扩展Ibatis.Net,面向接口编程-更优雅的代码

    使用Ibatis.Net做项目半年了,甚是喜欢,感觉确实是个简单.轻巧的O/R Mapping框架,特别是将Sql配置在Xml文件中,相当于直接将Dao层抽离了出来. 本文假定读者对Ibatis.Ne ...

  7. java~lambda表达式让查询更优雅

    在java之前的版本里,如果希望从集合时查找符合条件的数据,如果先遍历他,这种写法是我们不能接受的,所以现在java有了lambda就很好的解决了这个问题,让代码更优雅一些! /** * lambda ...

  8. MySQL root密码忘记,原来还有更优雅的解法!

    一直以来,对于MySQL root密码的忘记,以为只有一种解法-skip-grant-tables. 问了下群里的大咖,第一反应也是skip-grant-tables.通过搜索引擎简单搜索了下,无论是 ...

  9. JavaScript 复杂判断的更优雅写法

    我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃 ...

随机推荐

  1. BUUCTF-另一个世界

    另一个世界 010editor 打开最下方发现011开头字符串,应该是二进制 得到flag 看也有师傅写的是说八个一组转ascii码,现在也不是很理解啥意思.贴一下其他师傅的python脚本,算出的结 ...

  2. $.fn解析

    $.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例每一个有效. 如扩展$.fn.abc(),即$.fn.abc()是对jquery扩展了一个abc方法,那么后面你的每一个 ...

  3. JSP 入门学习

    概念:java Server Pages java服务器页面 一个特殊的,既可以定义html标签,也可以定义Java代码 用于简化书写 原理 在浏览器上访问 jsp文件时 服务器先解析请求,找到是否有 ...

  4. iOS OC纯代码企业级项目实战之我的云音乐(持续更新))

    简介 这是一个使用OC语言,从0使用纯代码方式开发一个iOS平台,接近企业级商业级的项目(我的云音乐),课程包含了基础内容,高级内容,项目封装,项目重构等知识:主要是讲解如何使用系统功能,流行的第三方 ...

  5. SELECT 的6大子句

    SELECT 6大子句的顺序: SELECT selection_list /*要查询的列名称*/, 结果的字段列表 FROM table_list /*要查询的表名称*/, 后面跟表,视图,多行多列 ...

  6. 什么是FastAPI异步框架?(全面了解)

    一:FastAPI框架 1.FastAPI是应该用于构建API的现代,快速(高性能)的 web 框架,使用Python 3.6+ 并基于标准的 Python 类型提示. 关键性: 快速: 可与Node ...

  7. 分支结构_嵌套if的使用

    嵌套if 语法结构: if 条件表达式1: if 内层条件表达式: 内层条件执行体1 else: 内存条件执行体2 else: 条件执行体 外层条件+内层条件的形式 example:#模拟打折的程序如 ...

  8. .NET的求复杂类型集合的差集、交集、并集

    前言 如标题所述,在ASP.NET应用程序开发中,两个集合做比较时 我们使用微软IEnumerable封装的 Except/Intersect/Union 取 差集/交集/并集 方法是非常的方便的: ...

  9. rsync 文件备份

    # rsync # 实现文件的备份. # 备份位置可以是当前主机,也可以是远程主机. # rsync实现了完全备份和增量备份 # 可以做到:1.将本地主机的文件复制到另一个位置(本地.远程). # 2 ...

  10. [GYCTF2020]Ezsqli-1|SQL注入

    1.打开界面之后在输入框进行输入测试,分别输入1.2.3.'等字符,结果如下: 2.看到bool(false)这里我想到了bool注入,因为之前做过这道题:https://www.cnblogs.co ...