You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
由于大多数的CSS-in-JS的库都是在动态生成CSS的。这会有两方面的影响。首先你发送到客户端的代码会包括使用到的CSS-in-JS运行时(runtime)代码,这些代码一般都不是很小,例如styled-components的runtime大小是12.42kB min + gzip,如果你希望你首屏加载的代码很小,你得考虑这个问题。其次大多数CSS-in-JS实现都是在客户端动态生成CSS的,这就意味着会有一定的性能代价。不同的CSS-in-JS实现由于具体的实现细节不一样,所以它们的性能也会有很大的区别,你可以通过这个工具来查看和衡量各个实现的性能差异
constshadow=document.querySelector('#hostElement').attachShadow({mode: 'open'});shadow.innerHTML='<sub-app>Here is some new text</sub-app><link rel="stylesheet" href="//unpkg.com/antd/antd.min.css">';
由于子应用的样式作用域仅在 shadow 元素下,那么一旦子应用中出现运行时越界跑到外面构建 DOM 的场景,必定会导致构建出来的 DOM 无法应用子应用的样式的情况。
highlight: a11y-dark
「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」
零:CSS的原生问题
在讲解各种解决方案之前,我们先回顾下日常开发中我们遇到的css问题,待着这些问题,我们在讲解各种解决方案,并分析各个解决方案是否可以解决如下问题
0.1 无作用域样式污染
CSS有一个被大家诟病的问题就是
没有本地作用域
,所有声明的样式都是全局的(global styles)
换句话来说页面上任意元素只要匹配上某个选择器的规则,这个规则就会被应用上,而且规则和规则之间可以
叠加作用(cascading)
SPA应用
流行了之后这个问题变得更加突出了,因为对于SPA应用来说所有页面的样式代码都会加载到同一个环境中,样式冲突的概率会大大加大。由于这个问题的存在,我们在日常开发中会遇到以下这些问题:.title
的样式名,这个类名很大概率已经或者将会和页面上的其他选择器发生冲突,所以你不得不手动为这个类名添加一些前缀,例如.home-page-title
来避免这个问题0.2 无用的CSS样式堆积
进行过大型Web项目开发的同学应该都有经历过这个情景:在开发新的功能或者进行代码重构的时候,由于
HTML代码和CSS样式之间没有显式的一一对应关系
,我们很难辨认出项目中哪些CSS样式代码是有用的哪些是无用的,这就导致了我们不敢轻易删除代码中可能是无用的样式。这样随着时间的推移,项目中的CSS样式只会增加而不会减少(append-only stylesheets)。无用的样式代码堆积会导致以下这些问题:0.3 基于状态的样式定义
对于SPA应用来说,特别是一些交互复杂的页面,页面的样式通常要根据组件的状态变化而发生变化
最常用的方式是通过不同的状态定义不同的
className名
,这种方案代码看起来十分冗余和繁琐,通常需要同时改动js代码和css代码
一、BEM
1.1 简介
BEM是
一种css命名方法论
,意思是块(Block)、元素(Element)、修饰符(Modifier)的简写这种命名方法让CSS便于统一团队开发规范和方便维护
以
.block__element--modifier
或者说block-name__element-name--modifier-name
形式命名,命名有含义,也就是模块名 + 元素名 + 修饰器名
如
.dropdown-menu__item--active
社区里面对BEM命名的褒贬不一,但是对其的思想基本上还是认同的,所以可以
用它的思想,不一定要用它的命名方式
1.2 应用场景
BEM思想通常用于
组件库
,业务代码中结合less等预处理器
1.3 优缺点分析
优点:
缺点
二、CSS modules
2.1 简介
什么是
CSS Modules
?顾名思义,
css-modules 将 css 代码模块化
,可以避免本模块样式被污染
,并且可以很方便的复用 css 代码根据
CSS Modules
在Gihub上的项目,它被解释为:所以
CSS Modules
既不是官方标准,也不是浏览器的特性,而是在构建步骤(例如使用Webpack,记住css-loader)中对CSS类名和选择器限定作用域
的一种方式(类似于命名空间)依赖
webpack css-loader
,配置如下,现在webpack已经默认开启CSS modules功能了我们先看一个示例:
将
CSS
文件style.css
引入为style
对象后,通过style.title
的方式使用title class
:对应
style.css
:打包工具会将
style.title
编译为带哈希的字符串
同时
style.css
也会编译:这样,就产生了独一无二的
class
,解决了CSS
模块化的问题使用了 CSS Modules 后,就相当于给每个 class 名外加加了一个
:local
,以此来实现样式的局部化,如果你想切换到全局模式,使用对应的:global
。:local
与:global
的区别是 CSS Modules 只会对:local
块的 class 样式做localIdentName
规则处理,:global
的样式编译后不变可以看到,依旧使用CSS,但使用JS来管理样式依赖,
最大化地结合现有 CSS 生态和 JS 模块化能力,发布时依旧编译出单独的 JS 和 CSS
2.2 优缺点分析
优点
缺点
styles.**
,可以试一下 [react-css-modules](gajus/react-css-modules · GitHub),它通过高阶函数的形式来避免重复输入styles.**
三、CSS in JS
3.1 简介
CSS in JS
是2014年推出的一种设计模式,它的核心思想是把CSS直接写到各自组件中
,也就是说用JS去写CSS
,而不是单独的样式文件里这跟传统的前端开发思维不一样,传统的原则是
关注点分离
,如常说的不写行内样式
、不写行内脚本
,如下代码CSS-in-JS
不是一种很新的技术,可是它在国内普及度好像并不是很高,它当初的出现是因为一些component-based
的Web
框架(例如React
,Vue
和Angular
)的逐渐流行,使得开发者也想将组件的CSS样式也一块封装到组件中去
以解决原生CSS写法的一系列问题上面的例子使用
React
改写如下上面代码在一个文件里面,封装了结构、样式和逻辑,完全
违背了"关注点分离"的原则
但是,这
有利于组件的隔离
。每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,很方便复用。所以,随着 React 的走红和组件模式深入人心,这种"关注点混合
"的新写法逐渐成为主流3.2 实现CSS in JS的库汇总
实现了
CSS-in-JS
的库有很多,据统计现在已经超过了61种。虽然每个库解决的问题都差不多,可是它们的实现方法和语法却大相径庭从实现方法上区分大体分为两种:
唯一CSS选择器
,代表库:styled-components内联样式
(Unique Selector VS Inline Styles)不同的
CSS in JS
实现除了生成的CSS样式和编写语法
有所区别外,它们实现的功能也不尽相同,除了一些最基本的诸如CSS局部作用域的功能,下面这些功能有的实现会包含而有的却不支持:3.3 styled-components示例
Styled-components 是
CSS in JS
最热门的一个库了,到目前为止github的star数已经超过了35k
通过
styled-components
,可以使用ES6的标签模板字符串语法(Tagged Templates)为需要styled
的Component
定义一系列CSS
属性当该组件的
JS代码被解析执行
的时候,styled-components会动态生成一个CSS选择器
,并把对应的CSS
样式通过style
标签的形式插入到head
标签里面。动态生成的CSS
选择器会有一小段哈希值来保证全局唯一性
来避免样式发生冲突CSS-in-JS Playground是一个可以快速尝试不同CSS-in-JS实现的网站,上面有一个简单的用

styled-components
实现表单的例子:从上面的例子可以看出,
styled-components
不需要你为需要设置样式的DOM节点设置一个样式名
,使用完标签模板字符串定义后你会得到一个styled
好的Component
,直接在JSX
中使用这个Component
就可以了可以看到截图里面框出来的样式生成了一段
hash值
,实现了局部CSS作用域
的效果(scoping styles),各个组件的样式不会发生冲突
3.4 Radium示例
Radium
和styled-components
的最大区别是它生成的是标签内联样式(inline styles)
由于标签内联样式在处理诸如
media query
以及:hover
,:focus
,:active
等和浏览器状态相关的样式的时候非常不方便,所以radium
为这些样式封装了一些标准的接口以及抽象再来看一下
radium
在CSS-in-JS Playground的例子:从上面的例子可以看出
radium
定义样式的语法和styled-components
有很大的区别,它要求你使用style
属性为DOM
添加相应的样式直接在标签内生成内联样式,内联样式相比于CSS选择器的方法有以下的优点:
自带局部样式作用域的效果
,无需额外的操作3.5 CSS in JS 与"CSS 预处理器"(比如 Less 和 Sass,包括 PostCSS)有什么区别
CSS in JS
使用JavaScript
的语法,是 JavaScript 脚本的一部分,不用从头学习一套专用的 API,也不会多一道编译步骤,但是通常会在运行时动态生成CSS,造成一定运行时开销3.6 优缺点分析
优点
没有无作用域问题样式污染问题
通过唯一CSS选择器或者行内样式解决
没有无用的CSS样式堆积问题
CSS-in-JS会把样式和组件绑定在一起,当这个组件要被删除掉的时候,直接把这些代码删除掉就好了,不用担心删掉的样式代码会对项目的其他组件样式产生影响。而且由于CSS是写在JavaScript里面的,我们还可以利用JS显式的变量定义,模块引用等语言特性来追踪样式的使用情况,这大大方便了我们对样式代码的更改或者重构
更好的基于状态的样式定义
CSS-in-JS会直接将CSS样式写在JS文件里面,所以样式复用以及逻辑判断都十分方便
缺点:
一定的学习成本
代码可读性差
大多数CSS-in-JS实现会通过生成唯一的CSS选择器来达到CSS局部作用域的效果。这些自动生成的选择器会大大降低代码的可读性,给开发人员debug造成一定的影响
运行时消耗
由于大多数的CSS-in-JS的库都是在动态生成CSS的。这会有两方面的影响。首先你发送到客户端的代码会包括使用到的CSS-in-JS运行时(runtime)代码,这些代码一般都不是很小,例如styled-components的runtime大小是
12.42kB min + gzip
,如果你希望你首屏加载的代码很小,你得考虑这个问题。其次大多数CSS-in-JS实现都是在客户端动态生成CSS的,这就意味着会有一定的性能代价。不同的CSS-in-JS实现由于具体的实现细节不一样,所以它们的性能也会有很大的区别,你可以通过这个工具来查看和衡量各个实现的性能差异不能结合成熟的CSS预处理器(或后处理器)Sass/Less/PostCSS,
:hover
和:active
伪类处理起来复杂四、预处理器
4.1 简介
CSS 预处理器是一个能让你通过预处理器自己独有的语法的程序
市面上有很多CSS预处理器可供选择,且绝大多数CSS预处理器会增加一些原生CSS不具备的特性,例如
这些特性让CSS的结构
更加具有可读性且易于维护
要使用CSS预处理器,你必须在web服务中安装CSS
编译工具
我们常见的预处理器:
4.2 优缺点分析
优点:
缺点
五、Shadow DOM
5.1 简介
熟悉

web Components
的一定知道Shadow DOM
可以实现样式隔离,由浏览器原生支持我们经常在微前端领域看到
Shadow DOM
,如下创建一个子应用由于子应用的样式作用域仅在
shadow
元素下,那么一旦子应用中出现运行时越界跑到外面构建 DOM 的场景,必定会导致构建出来的 DOM 无法应用子应用的样式的情况。比如
sub-app
里调用了antd modal
组件,由于modal
是动态挂载到document.body
的,而由于Shadow DOM
的特性antd
的样式只会在shadow
这个作用域下生效,结果就是弹出框无法应用到antd
的样式。解决的办法是把antd
样式上浮一层,丢到主文档里,但这么做意味着子应用的样式直接泄露到主文档了5.2 优缺点分析
优点
缺点
六、vue scoped
当
<style>
标签有scoped
属性时,它的CSS
只作用于当前组件中的元素通过使用
PostCSS
来实现以下转换:转换结果:
使用
scoped
后,父组件的样式将不会渗透到子组件中不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式,父租价利用
深度作用选择器
影响子组件样式可以使用
>>>
操作符:上述代码将会编译成:
有些像
Sass
之类的预处理器无法正确解析>>>
。这种情况下你可以使用/deep/
或::v-deep
操作符取而代之——两者都是>>>
的别名,同样可以正常工作总结
六种方案对比如下,社区通常的样式隔离方案,以下两种
BEM+预处理器
CSS Moduls + 预处理器
你用的CSS隔离方案是什么,欢迎探讨?
写在结尾
本文首发于zxyue25/github/blog,欢迎关注,star~,持续记录原创、好文~
The text was updated successfully, but these errors were encountered: