当前位置: 首页 > Web前端 > vue.js

比css_scoped和css_module更优雅的避免css命名冲突的方法

时间:2023-04-01 01:07:30 vue.js

css_scoped和css_module我们知道,简单的类名很容易造成重复的css命名,比如定义一个类:如果别人只是定义了一个className:.main,你的float:left会影响它。所以在Vue中发明了css_scoped,原理是在类名后面加一个数据属性选择器:.main{float:left;}//转义后变成css_scoped是Vue的特殊解决方案。如果你使用其他的UI框架,比如React,那么你可以使用一个更通用的css_module。原理是在样式名String后缀上加上hash字符,保证类名全局唯一:.main{float:left;}//转义后变成相比于css_scoped,css_module的方案更通用,不改变自身权重,渲染性能比前者好很多,所以推荐使用css_module。不过无论是css_scoped还是css_module,都有两大缺点:由于加入了随机字符,如果想在父组件中覆盖子组件中的样式就变得麻烦了,虽然css_scoped可以使用穿透,但是这个很容易导致其他问题。添加随机字符会使类名不那么优雅,也会影响编译速度。cssnamespace让我们回忆一下在css_scoped和css_module出现之前人们是如何避免css命名冲突的?没错,就是人为定义了一些css命名空间。届时,对于每一个Component组件,都会在其根节点上定义一个唯一的ID或类作为其命名空间,然后其内部的其他类将使用该命名空间作为前置条件,例如:

这样只要根节点的类不重复,其子节点的类就不会重复.对于一些全局样式,人们习惯于加一个g-作为命名空间,比如:这种依赖人为约定的css命名空间比较原始,但是有它的优点:简单有效,按照模块-组件名的命名规范,基本很容易保证不重复。样式名称更具语义。从任何dom开始,肯定能找到组件根节点的类名,基本就能猜出组件所在的业务模块和组件位置。父组件很容易使用权重覆盖子组件的任何样式。css_namespace+css_module如果把css_module和css_namespace结合起来,组件的命名空间由css_module自动生成,是不是更优雅的解决css冲突的方法?css_module中有2个特殊的作用域限定符::global这个限定符下的类名将保持原样,不会被css模块转换,例如::global{.test1{color:blue;}.test2{颜色:红色;}}//Compiled.test1{颜色:蓝色;}.test2{颜色:红色;}:local这个限定符下的类名将被css模块转换,例如::local{.test1{color:blue;}.test2{颜色:红色;}}//编译后.test1_3zyde4l1y{color:blue;}.test2_2DHwuiHWM{颜色:红色;如果我们使用css_namespace+css_module::global{:local(.root){>.hd{颜色:红色;.title{字体大小:18px;}}>.bd{颜色:蓝色;}}}//css编译意思是:原则上只有每个组件的根节点使用css_module自动生成一个不重复的类名,其余内部元素保持原名,不做任何转换(当然,在某些情况下,也可以使用多次转换),为了保证孙子的样式不影响别人,可以适当加上dom级别限制,比如>.hd,这样就只有子的.hd会受到影响。移除css_moudle随机字符rootnode上面的类名有个小hash尾巴,还是很不雅观的。其实hash字符只是为了保证name的全局唯一,你也可以通过其他方式来保证。如果你为项目设计了一个有意义的目录结构,你可以使用目录路径而不是哈希字符串。比如你的工程目录如下:src├──components│├──moduleA││├──componentX││├──componentY│├──moduleB││├──componentZ那么:components的目录路径-moduleA-componentX必须是全局唯一的,所以可以用这个路径来代替hash字符,css_module提供了自定义转换className的方法:typegetLocalIdent=(context:LoaderContext,localIdentName:string,localName:string):string;可以使用该方法将目录路径映射到类名,替换一些固定的目录,比如项目目录如下:src├──assets│├──css│├──global.module.scss//globalstyle│├──:local(.loading){}//globalstyle只需要加一个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是一个模块,名字是唯一的,内部结构遵循约定,所以可以简化为:user-LoginForm根据类名猜测文件位置。g-loading-前缀为g-,表示是全局类,对应的文件必须是src/assets/css/global.module.scss.comp-NavBar-前缀为comp-,表示是public组件,对应的组件必须是src/components/NavBar.user-LoginForm-根据约定,对应的组件必须是src/modules/user/components/LoginFormexample和sourcecode如果你也使用类似的项目目录,那么就可以直接使用我封装的路径映射函数getCssScopedName:const{getCssScopedName}=require('@elux/cli-utils');constsrcPath=path.resolve(__dirname,'../src');//webpackcss-loader{loader:'css-loader',options:{importLoaders:2,modules:{getLocalIdent:(context,localIdentName,localName)=>{returngetCssScopedName(srcPath,localName,context.resourcePath);},localIdentContext:srcPath,},},};当然你也可以自己实现个性化的getLocalIdent,无非就是正则匹配替换...css_namespace+css_module使用实际案例:基于antd的后台管理系统或者使用任意elux项目模板:npmcreateelux@latest或yarncreateelux如图,从类名基本可以推断出组件的位置。。。欢迎交流,我最近的文章:前端架构-分层有条理,铁定的MV流C手Router,什么路由器框架还需要else吗?让react-router/vue-router侧躺降温,不想当Window的Dialog不是好Modal,弹出窗口翻过来