把问题的结论放在开头,关于我的故事,感兴趣的同学或者觉得会对自己有帮助的同学慢慢欣赏吧,这样可以节省另外部分同学的时间。
1. css-modules是什么?
css-modules是将css赋予作用域概念的一种样式模块化解决方案。通过给样式名加hash字符串后缀的方式,实现特定作用域语境中的样式编译后的样式在全局唯一。
2. css-modules解决了什么问题?
当项目经由多位技术背景和水平参差不齐的开发者接手过后,项目的样式已经不能乖乖的被后面的同学搞了,不断的重构重构重构...... 嗯,是时候要有人拿小皮鞭来规范开发秩序了。css-modules的出现解决了全局样式污染,庞大的页面dom节点取名字的问题。
3. css-modules的科学实践
组件容器内样式作用域弱约束,页面区域间作用域强隔离。
例如页面结构为 header、main、aside、footer 这四个区域。header和aside共同使用了Navbar组件,Navbar的组件是复用了,样式一致,但是在header和aside中要有两种视觉效果。这时候把header和aside可以分别看做Navbar的两个实例容器,header和aside是用css-module管理的local作用域,在他们的local作用域中声明一个global作用域,通过.header :global .nav-bar{...}
的方式覆写组件样式,实现同一组件在同一页面不同区域的样式自定义。
4. 对css-modules出现背景所做的反思
css对于专业前端来说是很熟悉的,通过命名前缀,结构化命名来描述页面结构。但这些都是人为经验去做的代码管理,在团队开发资源紧张的情况下,很多不熟悉css的后端同学也需要尽快完成前端的任务,导致大量的样式冲突和冗余。于是乎,标准化组件给大家更少的选择,更少的关注css...
当然以上是出于许许多多后端、客户端发者对web前端的理解基础上给出的解决方案。语言之间的思想是会互相影响的,就那这个模块化的方案结合es6来说,我感觉就很像python了。
下面是关于我对css-modules使用经历的小故事:
不久前换了工作,开始使用React来开发web。早先就听闻过React的大名,之前的工作是团队中就我一只前端,考虑到效率问题,工作中一直在用Angular 1.X 来霸道的进行开发,功能大而全,即使是一个人来同时做前后端,也可以很快的开发出可维护性的系统。那么现在开始用了React,初次的开发体验我选择了"ant-design"。
根据ant-design的快速上手教程,我搭建起来一个初始化的项目结构。但是当我需要按照设计图去还原视觉效果的时候,我懵逼了。jsx的语法,加上模块化的React组件,在开发界面功能的时候很方便,有种类似客户端的控件引入方式。不过在我需要按照视觉设计图去覆写组件样式的时候,我发现竟然没有对应的css样式!
经过调试发现,项目结构中引入了css-modules,在浏览器的开发者工具中会看到,页面中节点的className被加上了hash字符串,这也就意味着css有了作用域的概念。后来查css-modules的文档,如果是全局作用域的样式需要在样式前面包一层 :global,代码如下:
:global {
html, body, #root {
height: 100%;
}
body { background: #fafafa; }
}
在没有:global包裹的样式,编译后的结果是一下这种:
MainLayout.less
.main{...}
index.html
<div id="root">
<div class="main"></div>
</div>
编译后的index.html
<div id="root">
<div class="main___1gjAI"></div>
</div>
MainLayout.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { DatePicker, message } from 'antd';
import styles from './MainLayout.less';
ReactDOM.render(
<div className={styles.main}>
<DatePicker onChange={value => this.handleChange(value)} />
</div>,
document.getElementById('root')
);
这里想要让css样式生效,必须在对应的节点使用 "module.style"(模
块.样式属性)的方式绑定样式。这里就没办法通过外部容器的选择器来定位组件节点了。因为组件是在自定义渲染部分引入使用,组件节点是自动渲染的,如果组件没有开放子节点的className字段,是无法修改组件样式的。
不过,如果在组件没有使用css-modules的情况下,我们可以通过在页面节点-组件容器的css作用域来创建一个组件容器的全局作用域(该作用域存在于页面节点的local作用域内,页面其他区域的css作用域是隔离的),代码如下:
.main{
:global{
.ant-calendar-picker{
/*这里可以覆写 DatePicker组件的样式了*/
}
}
}
在经过一阵惊讶和反思后,结合我之前的工作经历不禁开始赞叹css-modules的牛逼。
说他牛逼并不是因为他在技术上的实现有多么复杂,而是他可以让项目的不确定性大大降低,代码的可维护性得以提升。
以前也跟朋友吐槽过,相信前端同学有的也有过这样的经历,入职后发现自己是唯一的前端,而且接手的项目之前是后台的同学开发的。素质高点的js不会乱引的太严重,然而大多数后台的同学是不熟悉css的,样式不仅冗余,而且很多因为优先级而相互覆盖的问题。
换个角度想想就发现了css-modules的价值,就是tmd让不熟css的同学乖乖的不要给人家挖坑。如果说真的需要接手这类的项目,即便是损失了css的灵活性,我也是会开心的笑出眼泪吧(毕竟比反复重构人家写的页面要好很多)。