h1{color:red;font-size:46px;}1前言CSS是前端领域中发展最慢的部分。由于ES2015/2016的快速普及以及Babel/Webpack等工具的快速发展,CSS被远远甩在后面,逐渐成为大型项目工程化的痛点。也成为前端走向完全模块化前必须解决的难题。模块化解决了JS作用域的问题,但是CSS仍然存在样式覆盖的问题,因为最终打包最终会生成一个文件,而不是像开发时那样划分模块和作用域。因此,我们今天要讨论的是如何在模块化项目中安全地编写CSS样式,而不用担心样式覆盖问题。推荐的解决方案是CSSModule。二话不说1、CSS模块化遇到了什么问题CSS模块化重要解决两个问题:CSS样式的导入导出。样式导入:灵活按需导入,重用代码;styleexport:导出的时候需要能够隐藏内部作用域,避免全局污染。Sass/Less/PostCSS等尝试解决CSS编程能力弱的问题,也确实做得很好,但这并没有解决最重要的模块化问题。Facebook工程师Vjeux首先抛出了React开发中遇到的一系列CSS相关的问题。总结如下:全局污染CSS使用全局选择器机制来设置样式,优点是方便改写样式。缺点是所有样式全局生效,样式可能会被错误覆盖,所以会生成丑陋的!important,甚至inline!important和复杂的选择器权重计数表,增加出错概率和使用成本。WebComponents标准中的ShadowDOM可以完全解决这个问题,但是它的做法有点极端,样式完全本地化,导致无法在外部重写样式,失去了灵活性。命名混乱由于全局污染问题,为了避免多人协作开发时风格冲突,选择器越来越复杂,很容易形成不同的命名风格,难以统一。样式越多,命名就越混乱。不完整的依赖管理组件应该相互独立。引入组件时,只引入其需要的CSS样式即可。但是目前的做法是在JS之外引入它的CSS,Saas/Less很难为每个组件单独编译一个CSS,引入所有模块的CSS很浪费。JS的模块化已经很成熟了,如果能用JS来管理CSS依赖是一个很好的解决方案。Webpack的css-loader提供了这种能力。无法共享变量。复杂的组件需要使用JS和CSS共同处理样式,这会导致一些变量在JS和CSS中出现冗余。Sass/PostCSS/CSS等不提供跨JS和CSS共享变量的能力。代码压缩不完全由于移动网络的不确定性,CSS压缩已经达到了变态的地步。许多压缩工具会将'16px'转换为'1pc'以节省一个字节。但它对非常长的类名无能为力,而且权力使用不当。上述问题仅靠CSS本身是无法解决的。如果通过JS来管理CSS,就可以轻松解决。因此,Vjuex给出的解决方案是在JS中完整的CSS,但是这相当于完??全放弃了CSS。在JS中要用Object语法来写CSS,估计刚刚看过的小伙伴都会被吓一跳。直到CSS模块出现。2.CSS方案概述CSS模块化方案有很多,但主要有两种:完全抛弃CSS,使用JS或JSON编写样式Radium,jsxstyle,react-style就属于这一类。优点是可以为CSS提供和JS一样强大的模块化。缺点是不能利用成熟的CSS预处理器(或后处理器),Sass/Less/PostCSS,:hover和:active伪类处理复杂。另一种是仍然使用CSS,但是使用JS来管理样式依赖,以CSSModules为代表。CSSModules可以最大限度的结合已有的CSS生态和JS模块化能力,API简单,几乎没有学习成本。发布仍然编译出单独的JS和CSS。它不依赖于React,只要你使用Webpack,它就可以与Vue/Angular/jQuery一起工作。3、为什么是CSSModuleCSSModules可以最大限度的结合已有的CSS生态和JS模块化能力,而且API简单到几乎没有学习成本。发布仍然编译出单独的JS和CSS。它不依赖于React,只要你使用Webpack,它就可以与Vue/Angular/jQuery一起工作。4、使用CSSModuleCSSModules内部使用ICSS来解决样式导入导出两个问题。分别对应:import和:export这两个新的伪类。:import("path/to/dep.css"){localAlias:keyFromDep;/*...*/}:export{exportedKey:exportedValue;/*...*/}但是直接使用这两个关键字进行编程太麻烦了,在实际项目中很少直接使用,我们需要的是用JS管理CSS的能力。结合Webpack的css-loader,可以在CSS中定义样式,在JS中导入。EnableCSSModules//webpack.config.jscss?modules&localIdentName=[name]__[local]-[hash:base64:5]加上modules启用,localIdentName是设置生成样式的命名规则。也可以这样设置:test:/\.less$/,use:['style-loader',{loader:'css-loader',options:{modules:true,localIdentName:'[name]__[local]-[hash:base64:5]',},},],},样式文件Button.css:.normal{/*所有与normal相关的样式*/}.disabled{/*所有与disabled相关的样式*/}/*components/Button.js*/importstylesfrom'./Button.css';console.log(styles);buttonElem.outerHTML=`提交`generatedHTML为提交注意button--normal-abc53是CSSModules根据localIdentName自动生成的类名。其中abc53是根据给定算法生成的序列码。经过这样的混淆,类名基本是唯一的,大大降低了项目中样式覆盖的几率。同时修改生产环境中的规则,生成更短的类名,可以提高CSS的压缩率。在上面的例子中,控制台打印的结果是:Object{normal:'button--normal-abc53',disabled:'button--disabled-def884',}CSSModules已经处理了CSS中的所有类名,使用对象保存原始类和混淆类之间的对应关系。通过这些简单的流程,CSSModules实现了以下几点:所有样式都是本地的,解决了命名冲突和全局污染的问题。类名生成规则配置灵活,可以用来压缩类名。只需引用组件的JS即可。组件的所有JS和CSS依然是CSS,几乎0学习成本默认在本地使用CSSModules后,相当于在每个类名上加一个:local,实现样式的本地化。要切换到全局模式,请使用相应的:global。.normal{color:green;}/*上面等同于下面的*/:local(.normal){color:green;}/*定义全局样式*/:global(.btn){color:red;}/*定义多个全局样式*/:global{.link{color:green;}.box{颜色:黄色;}}Compose组合样式对于样式复用,CSSModules只提供了唯一的处理方式:composescombination/*components/Button.css*/.base{/*所有常用样式*/}.normal{composes:base;/*正常其他样式*/}.disabled{composes:base;/*禁用其他样式*/}importstylesfrom'./Button.css';buttonElem.outerHTML=`Submit`生成的HTML变为提交因为.base是在.normal中合成的,normal编译后会变成两个类。composes还可以从外部文件中组合样式。/*settings.css*/.primary-color{color:#f40;}/*components/Button.css*/.base{/*所有常用样式*/}.primary{composes:base;组成:来自'./settings.css'的原色;/*其他主要样式*/}对于大多数项目,组合不再需要Sass/Less/PostCSS。但是如果要用的话,因为composes不是标准的CSS语法,编译的时候会报错。您只能使用预处理器自己的语法进行样式重用。类命名技巧CSSModules命名约定是从BEM扩展而来的。BEM将样式名称分为三个层次,分别是:Block:对应模块名称,比如DialogElement:对应模块中的节点名称ConfirmButtonModifier:对应节点的状态,比如disabled,highlight总结,BEM最终得到的类名为dialog__confirm-button--highlight。双符号__和--用于将它们与块中单词之间的分隔符区分开来。看起来很奇怪,BEM被大量的大型项目和团队采用。我们在实践中也认可这种命名方式。CSSModules中的CSS文件名与Block名称完全对应,只需要考虑Element和Modifier。BEM对应CSSModules的方式是:/*.dialog.css*/.ConfirmButton--disabled{}也可以使用camelCase将Block和Modifier放在一起,无需遵循完整的命名规范:/*.dialog.css*/...CSSModules中的概念,这里的CSS变量是指Sass中的变量。上面提到的:export关键字可以将CSS中的变量导出到JS中。下面是如何在JS中读取Sass变量:/*config.scss*/$primary-color:#f40;:export{primaryColor:$primary-color;}/*app.js*/importstylefrom'config.scss';//将输出#F40console.log(style.primaryColor);CSSModules使用技巧CSSModules是对现有CSS的减法。为了追求简洁可控,作者建议遵循以下原则:不使用选择器,只使用类名定义样式不级联多个类,只用一个类定义所有样式所有样式通过composes组合起来实现复用和非嵌入应用以上两个原则相当于弱化了样式中最灵活的部分,初学者很难接受。第一个不难练习,但是第二个,如果模块状态太多,类的数量会呈指数增长。请注意,以上称为建议,因为CSS模块不会强制您这样做。听起来很矛盾,因为大多数CSS项目都有很深的遗留问题,太多的限制意味着增加迁移成本和与外部各方合作的成本。在早期肯定会有一些妥协。幸运的是,CSS模块在这方面做得很好:如果我为一个元素使用多个类怎么办?没问题,样式仍然有效。如何在样式文件中使用同名类?没问题,这些同名的类编译后可能是乱码,但还是同名。如果在样式文件中使用伪类、标签选择器等怎么办?没问题,所有这些选择器都不会被转换,并且会原封不动地出现在编译后的css中。也就是说,CSSModules只会转换类名和id选择器名相关的样式。但注意以上3个“如果”尽量不要发生。CSSModules结合React实践首先,在CSSloader中开启CSSModule:{loader:'css-loader',options:{modules:true,localIdentName:'[local]',},},直接使用cssatclassName的类名字就可以了。/*dialog.css*/.root{}.confirm{}.disabledConfirm{}从'classnames'导入类名;从'./dialog.css'导入样式;导出默认类DialogextendsReact.Component{render(){constcx=classNames({[styles.confirm]:!this.state.disabled,[styles.disabledConfirm]:this.state.disabled});returnConfirm...
}}注意,一般组件最外层节点对应的类名是root。这里使用classnames库来操作类名。如果不想频繁敲styles.xx,可以试试react-css-modules,它可以避免以高阶函数的形式重复敲styles.xx。注意??React中使用CSSModule,可以设置5.注意几点1.多个class的情况下//可以使用字符串拼接className={style.oneclass+''+style.twoclass}//可以使用es6stringsTemplateclassName={`${style['calculator']}${style['calculator']}`}2.如果class使用连字符,可以使用数组style['box-text']3.Aclass是父组件传下来的。如果一个类是从父组件传下来的,那么父组件已经使用了样式的改变,不需要再在子组件中改变样式。示例如下://父组件中render