看element-ui源码后,获取的知识点

515 阅读4分钟

1. HTML5中的aria与role

element很多组件有会带上aria和role属性, 比如:Switch组件

这些都是HTML5针对html tag增加的属性,一般是为不方便的人士提供的功能,比如屏幕阅读器。 role的作用是描述一个非标准的tag的实际作用。比如用div做switch,那么设置div 的 role="switch",辅助工具就可以认出这实际上是个switch。 aria的意思是Accessible Rich Internet Application,aria-*的作用就是描述这个tag在可视化的情境中的具体信息。比如: 以上图为例,辅助工具就会知道, 这个div实际上是个switch的角色,为选中状态。

2.基于scss的bem方法的实现

首先来看一个bem命名示例

.el-message-box{}
.el-message-box__header{}
.el-message-box__header--active{}

如果使用已经封装好的bem方法的话,那么可以写成

@include b('message-box') {
    @include e('header') {
        @include m('active');
    }
}

bem方法是如何实现的?

bem方法解析

首先找到packages/theme-chalk/src/mixins/config.scss文件,里面定义了如下几个变量

$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';

然后我们再找到packages/theme-chalk/src/mixins/mixins.scss文件,找到b,e,m方法

/* 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 + & + $modifier-separator + $unit + ","};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

代码量不多,逻辑也不复杂,但是语法有点晦涩难懂,接下来我们一个一个解释

  • !global 变量提升,将局部变量提升为全局变量,在其他函数体内也能访问到此变量
  • @at-root将父级选择器直接暴力的改成根选择器
.header{
    @at-root {
        .content{color:red}
    }
}

编译为

.header{}
.content{color:red}
  • #{}插值,可以通过 #{} 插值语法在选择器和属性名中使用 SassScript 变量
$name: foo;
$attr: border;
p.#{$name} {
      #{$attr}-color: blue;
}

编译为

p.foo {
  border-color: blue;
}

现在我们在重新看一下b方法,定义了一个全局变量,拼接了一下字符串,逻辑很简单,e方法稍微复杂点,调用了一个hitAllSpecialNestRule方法(判断父级选择器是否包含'--','is',':'),hitAllSpecialNestRule定义在packages/theme-chalk/src/mixins/function.scss文件中,代码如下:

@import "config";

/* 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);
}

inspect:以字符串的形式返回表达式

@debug meta.inspect(10px 20px 30px); // unquote("10px 20px 30px")
@debug meta.inspect(("width": 200px)); // unquote('("width": 200px)')
@debug meta.inspect(null); // unquote("null")
@debug meta.inspect("Helvetica"); // unquote('"Helvetica"')

Maps不能转换为纯CSS。作为变量的值或参数传递给CSS函数将会导致错误。使用inspect($value) 函数以产生输出字符串,这对于调试 maps 非常有用。 重新回到e方法中,也是先拼字符串,然后再判断父级class是否存在嵌套关系,然后输出结果。

.container {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
        }
    }
}
.container--fix {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
        }
    }
}

编译为

.container .el-button {
  width: 200px;
  height: 200px;
}
.el-button__body {
  color: #ccc;
}
.container--fix .el-button {
  width: 200px;
  height: 200px;
}
.container--fix .el-button .el-button__body {
  color: #ccc;
}

最后一个m方法,流程和e一致,区别在拼接currentSelector字符串时,使用了$父级选择器,还没有使用全局变量B+全局变量E来拼接,因为结构不一定是B-E-M,有可能是B-M。最后附上完整的编译结果

.container {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}
.container--fix {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}

编译为

.container .el-button {
  width: 200px;
  height: 200px;
}
.el-button__body {
  color: #ccc;
}
.container--fix .el-button {
  width: 200px;
  height: 200px;
}
.container--fix .el-button .el-button__body {
  color: #ccc;
}

3. vue 中 provide 和 inject 用法

  • 2.2.0 新增

  • 类型:

    • provide:Object | () => Object
    • inject:Array | { [key: string]: string | Symbol | Object }
  • 详细:

    provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

inject 选项应该是:

  • 一个字符串数组,或
  • 一个对象,对象的 key 是本地的绑定名,value 是:
    • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    • 一个对象,该对象的:
      • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
      • default property 是降级情况下使用的 value
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

element-ui 中 使用示例

form.vue

对子孙组件提供当前“this”

switch.vue

在switch.vue里接收elFrom, 获取from.vue组件相关数据,比如:this.elForm.disabled等

实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了: 祖先组件不需要知道哪些后代组件使用它提供的属性 后代组件不需要知道被注入的属性来自哪里