• 结合Elementplus源码讲解BEM的使用


    前言

    早就听说过使用BEM命名管理样式,平时在项目中也有用到,但总感觉写起来太麻烦,最近刚好在看Elementplus源码,发现Elementplus使用BEM的方案非常棒,本文就来结合ElementPlus源码分析一下如何在项目中优雅的使用BEM命名

    BEM的介绍

    BEM是一种针对css的前端命名规范,**是块(Block),元素(Element),修饰符(Modifier)**的简写。

    Block可以理解为模块,比如:article,dialog,sidebar,form,tab

    Element可以理解为块里的元素,比如form里面的input,submit

    modifier可以理解为对block或者element的修饰,比如修饰form__submit–disable,form–theme-dark

    BEM的命名规范

    .block { }
    .block__element { }
    .block--modifier { }
    .block__element--modifier { } 
    
    • 1
    • 2
    • 3
    • 4

    使用__来连接block和element,使用–来连接block和modifier

    清楚了概念和命名规范,下面我们就来看ElementPlus是怎么实现BEM

    BEM的实践

     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    代码很简单,一看就懂,不做过多解释,下面看用ElementPlus的方法如何进行改造

    useNamespace

    首先定义一个工具方法useNamespace,此函数返回了符合BEM命名规则的方法

    // useNamespace.ts
    import { computed, unref } from 'vue'
    ​
    // 定义一个组件的命名前缀
    const defaultNamespace = 'poliwen'
    ​
    // 定义一个描述组件状态的变量
    const statePrefix = 'is-'
    ​
    // 定义个_bem方法,此方法返回符合BEM规范的命名
    const _bem = ( namespace: string,
      block: string,
      blockSuffix: string,
      element: string,
      modifier: string, ) => {
      let cls = `${namespace}-${block}`
      if (blockSuffix)
        cls += `-${blockSuffix}`
    ​
      if (element)
        cls += `__${element}`
    ​
      if (modifier)
        cls += `--${modifier}`
    ​
      return cls
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    // useNamespace.ts
    // 定义一个命名空间,并且返回BEM方法
    export const  useNamespace = (block: string) => {
      const namespace = computed(() => defaultNamespace)
      const b = (blockSuffix = '') =>
        _bem(unref(namespace), block, blockSuffix, '', '')
      const e = (element?: string) =>
        element ? _bem(unref(namespace), block, '', element, '') : ''
      const m = (modifier?: string) =>
        modifier ? _bem(unref(namespace), block, '', '', modifier) : ''
      const be = (blockSuffix?: string, element?: string) =>
        blockSuffix && element
          ? _bem(unref(namespace), block, blockSuffix, element, '')
        : ''
      const em = (element?: string, modifier?: string) =>
        element && modifier
          ? _bem(unref(namespace), block, '', element, modifier)
        : ''
      const bm = (blockSuffix?: string, modifier?: string) =>
        blockSuffix && modifier
          ? _bem(unref(namespace), block, blockSuffix, '', modifier)
        : ''
      const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
        blockSuffix && element && modifier
          ? _bem(unref(namespace), block, blockSuffix, element, modifier)
        : ''
      const is: {
      (name: string, state: boolean | undefined): string
      (name: string): string} = (name: string, ...args: [boolean | undefined] | []) => {
        const state = args.length >= 1 ? args[0]! : true
        return name && state ? `${statePrefix}${name}` : ''}
      return {
        namespace,
        b,
        e,
        m,
        be,
        em,
        bm,
        bem,
        is,}
    }
    ​
    export type UseNamespaceReturn = ReturnType
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    useNamespace库的使用

    import { useNamespace } from './compo/useNamespace'
    const bs = useNamespace('dialog')
    ns.b() // el-dialog
    ns.b('overlay') // el-dialog-overlay
    ns.e('header') // el-dialog__header
    ns.m('theme-dark') // el-dialog--theme-dark
    ns.be('header','close') // el-dialog-header__close
    ns.em('footer','small') // el-dialog__footer--small
    ns.bm('footer','small') // el-dialog-footer--small
    ns.bem('footer','btn','primary') // el-dialog-footer__btn--primary
    ns.is('closeable') // is-closeable 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ns.b() 返回结果为 "el-dialog "

    ms.b(‘overlay’)

    ns.e(‘header’) 返回结果为 “el-dialog__header”

    ns.m(‘theme-dark’) 返回结果为 “el-dialog–theme-dark”

    ns.be(‘header’,‘close’) 返回结果为 “el-dialog-header__close” 意思为返回一个block + element

    ns.em('foter,‘small’) 返回结果为 “el-dialog__footer–small” 意思为返回一个element + modifier

    ns.bm(‘footer’,‘small’) 返回结果为 “el-dialog-footer–small” 意思为返回一个block + modifier

    ns.bem(‘footer’,‘btn’,‘primary’) 返回结果为" el-dialog-footer__btn–primary" 意思为返回一个block+element+modifer

    ns.is(‘closeable’) 返回is-closeable 通常用来描述组件的状态,如is-closeable 表示:是否显示关闭按钮

    知道了useNamespace库的用法,下面我们来改造dialog.vue的html模板

    改造前的代码

     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    改造后的代码

     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    scss也需要改造,新增了一个$nameSpace命名空间

     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    BEM的mixins的封装

    我们还可以将BEM的命名封装成scss的mixins方法

    $namespace: 'poliwen';
    $common-separator: '-';
    $element-separator: '__';
    $modifier-separator: '--';
    $state-prefix: 'is-';
    $namespace: 'el';
    // BEM support Func
    @function selectorToString($selector) {
      $selector: inspect($selector);
      $selector: str-slice($selector, 2, -2);
      @return $selector;
    }
    ​
    @function containsModifier($selector) {
      $selector: selectorToString($selector);
    ​
      @if str-index($selector, $modifier-separator) {
        @return true;} @else {
        @return false;}
    }
    ​
    @function containWhenFlag($selector) {
      $selector: selectorToString($selector);
    ​
      @if str-index($selector, '.' + $state-prefix) {
        @return true;} @else {
        @return false;}
    }
    ​
    @function containPseudoClass($selector) {
      $selector: selectorToString($selector);
    ​
      @if str-index($selector, ':') {
        @return true;} @else {
        @return false;}
    }
    ​
    @function hitAllSpecialNestRule($selector) {
      @return containsModifier($selector) or containWhenFlag($selector) or
        containPseudoClass($selector);
    }
    ​
    ​
    // bem('block', 'element', 'modifier') => 't5-block__element--modifier'
    @function bem($block, $element: '', $modifier: '') {
      $name: $namespace + $common-separator + $block;
    ​
      @if $element != '' {
        $name: $name + $element-separator + $element;}
    ​
      @if $modifier != '' {
        $name: $name + $modifier-separator + $modifier;}
    ​
      // @debug $name;
      @return $name;
    }
    ​
    // BEM
    @mixin b($block) {
      $B: $namespace + "-" + $block !global;
    ​.#{$B} {
        @content;}
    }
    ​
    @mixin e($element) {
      $E: $element !global;
      $selector: &;
      $currentSelector: "";
      @each $unit in $element {
        $currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};}
    ​
      @if hitAllSpecialNestRule($selector) {
        @at-root {
        #{$selector} {
          #{$currentSelector} {
              @content;
          }
        }
      }} @else {
        @at-root {
        #{$currentSelector} {
            @content;
        }
      }}
    }
    ​
    @mixin m($modifier) {
      $selector: &;
      $currentSelector: "";
      @each $unit in $modifier {
        $currentSelector: #{$currentSelector + $selector + $modifier-separator + $unit + ","};}
    ​
      @at-root {
      #{$currentSelector} {
          @content;
      }}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    以上代码稍稍有点复杂,感兴趣的可以去学习下scss的高级用法

    定义好mixins方法之后,我们最终的样式代码如下

     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    总结

    BEM样式命名是各大主流组件库的常用命名方法,因此掌握它的使用很有必要,本文主要结合elementPlus的源码分析了BEM的妙用,通过定义一个useNamespace工具库,来生成符合BEM命名规范的方法,使用scss编写符合BEM命名的mixins,在组件中结合useNamespace和minxins就能很方便的使用BEM命名大法。
    )

    最后

    为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    Mysql的日志系统 redo log 和binlog
    k8s基本概念
    【深度学习】python调用超分Real-ESRGAN
    8.1.9:Extreme Optimization Numerical Libraries for .NET
    nginx的location的 优先级和匹配方式
    前端框架BootStrap
    无法调试MFC源码
    sql登录操作笔记
    SpringBoot + EasyExcel 实现表格数据导入
    【python技巧】替换文件中的某几行
  • 原文地址:https://blog.csdn.net/web2022050901/article/details/127739692