css中的BEM规范
bem是css中一种给类命名的规范,elementui就是按照bem规范给组件命名的。bem含义:
- Block 代表一个块,例如一个input组件,就是一个组件块,有层级用
-
分割:el-input
- Element 代表某个块里的元素之一,如块的外壳部分,以
__
与块间隔:el-input__wrapper
- Modifier 用来修饰块或元素,代表它们的类型,以
--
与被修饰块或元素间隔:el-buttom--primary
/* 块命名层级表示用 - */
.tag-block {}
/* 块里的元素表示所属块用 __ */
.tag-block__header {}
/* 元素的样式修改用 -- */
.tag-block__header--primary {}
.tag-block__bottom--blue {}
如果使用sass来编写类通常会使用嵌套来表示层级关系,但是编译符合bem规范的css类是无需嵌套的。
如,.tag-block .tag-block__header{ color:red; }
,类命名已经有层级信息了,无需再嵌套,应该直接写为.tag-block__header{ color:red; }
。
sass提供了@at-root
来修饰类,可以在sass中表示类的层级关系,但把类编译成css时,会把它提到顶层,解除嵌套。
/*scss*/
.el-input {
color: red;
@at-root .el-input__wraper { color: green; }
}
/*编译为css*/
.el-input { color: red; }
.el-input__wraper { color: green; }
vue3项目中使用BEM规范
bem作为css规范,可在任意项目中使用。
由于bem规范中使用到了像- __ --
,这些固定分割符号,为了提高复用性和规范性,本文会展示如何在vue3中,自定义sass函数,来为我们使用指定的分隔符进行拼接类名。
同时为了在js中同步sass这些类名,会配置sass变量的导出,和在js中导入这些变量。为了提高复用,在js同样封装了与sass函数功能一样的,拼接类名的函数,并挂载到vue实例上。
使用vite作为构建工具
定义sass函数,自动拼接为符合规范的类名
-
安装sass依赖
npm i sass -D
; -
使用sass提供的函数功能,定义拼接符合bem命名规范的sass函数
// ./src/bem.scss $block-split: "-" !default; // 块分割符,!default代表无需访问其它变量,它就是一个独立的值 $element-split: "__" !default; // 元素分割符 $modifier-split: "--" !default; // 修饰分割符 $namespace: 'el' !default; // 命名空间,通常是项目名 //混入函数,自动拼接类名:el-xxx // xxx为$block对应的实参 @mixin b($block) { $B: $namespace + $block-split + $block; //局部变量 //插值语法#{} .#{$B} { @content; //内容替换 } } /* * 使用示例 * * @include b(input) { * color: green; * } * 编译为 .el-input {color: green;} */ //混入函数,自动拼接类名,并与父级同级不嵌套:__xxx @mixin e($element) { // @at-root表示生成的类不会嵌套 @at-root { // 拼接&,和上一层类进行类名拼接即:&__xxx #{& + $element-split + $element} { @content; } } } /* 使用示例,(ps:必须嵌套在块内部): @include b(input) { e(wraper) { color: red; } } 编译为: .el-input__wraper { color: red;} */ //混入函数,自动拼接类名,并与父级同级不嵌套:--xxx @mixin m($modifier) { @at-root { #{& + $modifier-split + $modifier} { @content; } } }
-
为了无需手动在每个组件引入上述bem.css中的函数,可直接配置vite.config.css,来为每个组件引入bem.sass
// ./vite.config.ts import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), ], css: { preprocessorOptions: { scss: { // 为每个组件的scss类型的style标签引入./src/bem.scss additionalData: '@use "./src/bem.scss" as *;' } } }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })
-
在组件里书写bem规范的类名
<!-- ./src/component/DemoSass.vue --> <script setup lang="ts"> import My from './components/My.vue' </script> <template> <My></My> <div class="el-title el-title--primary"> <div :class="el-title__wraper">小明</div> </div> </template> <style lang="scss"> @include b(title) { color: grey; @include e(wraper) { border: 1px solid black } @include m(primary) { background-color: #fff; } } /* 编译结果 .el-title { color: grey; } .el-title__wraper { border: 1px solid black; } .el-title--primary { background-color: #fff; } */ </style>
将sass中的变量导出,在js中获取,并拼接为类名
sass代码中,可使用sass函数来灵活拼接类名,而标签中却要写死类名,书写sass函数的意义就不大了。
可以将sass中的变量导出,让js也能访问。在js中拼接为和sass中一致的类名。
-
使用
:export
将sass变量导出// ./src/config.module.scss @use './bem.scss' as *; // 导入上文的bem.scss,这样就可以访问bem.scss里面定义的变量 :export { b: $block-split;// $block-split是bem.scss中定义的变量 e: $element-split; m: $modifier-split; namespace: $namespace; }
-
js中导入sass导出的变量
由于sass导出字符串的变量时,会把双引号带上,所以在js中使用时,要把双引号去掉
// ./src/handleScssVar.ts 名字自定义即可 import config from './config.module.scss'; // 导入sass中导出的变量 const scssVarObj: any = Object.create(null); // 注意sass导出时,会把变量的双引号也带上:如 {b : "\"-\""}, // sass会把双引号也当成变量的一部分导出来,这里把双引号去掉 Object.keys(config).map((item: string) => scssVarObj[item] = config[item].slice(1, -1)) export default scssVarObj; // 这样导出的对象,就是js中正常的值,如{b: '-'} // 或者直接导出几个函数,直接返回拼接好的类名 /** * * @param blockName 块的名称 * @returns 返回命名空间拼接块名的类名,如:el-xxx */ export function blockClass(blockName: string): string { return scssVarObj.namespace + scssVarObj.b + blockName } /** * * @param blockName 块的名称 * @param elementName 块内部元素的名称 * @returns 依次拼接命名空间、块、块内元素的类名,如:el-xxx__wraper */ export function elementClass(blockName: string, elementName: string): string { return blockClass(blockName) + scssVarObj.e + elementName } /** * * @param blockName 块的名称 * @param modifierName 修饰块的类型 * @returns 依次拼接命名空间、块、块类型修饰的类名,如:el-xxx--primary */ export function modifierClass(blockName: string, modifierName: string): string { return blockClass(blockName) + scssVarObj.m + modifierName }
-
为了不需要在每个vue文件中导入上面的js文件,就可以访问这些函数,可以把它们挂到vue实例上
// ./main.ts 主入口文件 import { createApp } from 'vue' import App from './App.vue' import { blockClass,elementClass,modifierClass } from './handleScssVar'; const app = createApp(App); // 挂到vue实例上 app.config.globalProperties.$blockClass = blockClass; app.config.globalProperties.$elementClass = elementClass; app.config.globalProperties.$modifierClass = modifierClass; app.mount('#app')
-
此时,通过this访问这些函数是可以访问的,但在ts中会报错,因为vue模块的ts声明文件没有这几个接口,需要我们声明一下
// ./vueExtend.d.ts // 防止覆盖全局。 export {} declare module 'vue' { // 扩展vue模块上的接口 interface ComponentCustomProperties { $blockClass: (key: string) => string; $modifierClass(blockName: string, modifierName: string): string; $elementClass(blockName: string, elementName: string): string } }
-
最后使用时,上面的组件可以改为
<!-- ./src/component/DemoSass.vue --> <script setup lang="ts"> import My from './My.vue' </script> <template> <My></My> <div :class="[$blockClass('title'), $modifierClass('title', 'primary')]"> <div :class="$elementClass('title', 'wraper')">小明</div> </div> </template> <style lang="scss"> @include b(title) { color: grey; @include e(wraper) { border: 1px solid black } @include m(primary) { background-color: #fff; } } /* 编译结果 .el-title { color: grey; } .el-title__wraper { border: 1px solid black; } .el-title--primary { background-color: #fff; } */ </style>