Element-UI 源码简析——select(上)

26d8116a7588ac4a91a8398280e436fe.png

序言

距离上次发布文章转眼已经快一个月了,最近因为项目的原因比较繁忙,,所有一直断更,但是不要在意这些小细节,步入正题。

按照顺序来说,这次轮到的Select下拉选择器,这个我简单的瞄了一眼的确挺复杂的,所以的话呢 这个select可能要花好几篇来分享这个,反正我看了一下代码 难不难不知道 东西是真的多啊。单单一个select.vue就快1000行了,整体加起来的话可以会冲2000多行吧,但是吧 虽然代码多,但是值得学习的点还是非常的多的。

结构分析

select 组件含有navigation-mixin.js,option-group.vue,option.vue,select-dropdown.vue,select.vue等文件

还是和往常的一样 咱们按照三种形式来分析 但是这次会从多个方面来进行分析,同样也会带着问题来进行看select的代码

因为是想了解的比较透彻一点,所有本章主要内容是分享一些主体的东西

Dom结构

 <div class="el-select">
    // 多选时用el-tag的形式来展示多选的文字
    <div class="el-select__tags">
      <span>
      // 这边两个tag一个是展示当前多选的第一个 
      // 另一个是展示 +1 这样的形式展示(官网上 基础多选 第一个的样子)
        <el-tag></el-tag>
        <el-tag></el-tag>
      </span>
      
      // 把每个选中的标签循环展示出来
      <transition-group @after-leave="resetInputHeight" v-if="!collapseTags">
        <el-tag></el-tag>
      </transition-group>
      
      // 可输入文本的查询框 
      <input type="text" class="el-select__input">
    </div>
    
     // 单选时的回显
     <el-input ref="reference" v-model="selectedLabel" type="text">
          <template slot="prefix" v-if="$slots.prefix">
               <slot name="prefix"></slot>
          </template>
          // 清除和下箭头按钮
          <template slot="suffix">   这里两个小按钮分别用了show和if
               <i v-show="!showClose" ></i>
               <i v-if="showClose"></i>
          </template>
     </el-input>

      // 下拉菜单
      <el-select-menu>
        <el-scrollbar>
           // 下拉的每个选项
          <el-option></el-option>
          <slot></slot>
        </el-scrollbar>
        
        // 为空时或者loading时的处理
        <template>
          <slot></slot>
          <p></p>
        </template>
        
      </el-select-menu>
  </div>
复制代码

大致的Dom结构就是这样了,其实吧,只要把外层几个大标签分一下类别,通过对应的class和那些属性的逻辑判断就可以看的出来每个对应的Dom标签是干什么的。

用法功能和问题分析

一 、功能用法

  • 基础用法
  • 有禁用选项
  • 禁用状态
  • 可清空单选
  • 基础多选
  • 自定义模板
  • 分组
  • 可搜索
  • 远程搜索
  • 创建条目

二、问题分析

  • 如何处理父子组件的通信的呢?
  • 下拉组件是怎么实现的?

数据属性和事件

 这里就不单独把数据属性和事件拿出来讲了 用到什么就写什么吧,毕竟代码太多了 总共加起来好几千行!!!
 
复制代码

基础用法

基础用法所能用到的Dom

       <el-input
           v-model="selectedLabel"
           :placeholder="currentPlaceholder"
           :autocomplete="autoComplete || autocomplete"
           :size="selectSize"
           :disabled="selectDisabled"
           :readonly="readonly"
           :validate-event="false"
           :class="{ 'is-focus': visible }"
           :tabindex="multiple && filterable ? '-1' : null"
           @focus="handleFocus"
           @blur="handleBlur"
           @keyup.native="debouncedOnInputChange"
           @keydown.native.down.stop.prevent="navigateOptions('next')"
           @keydown.native.up.stop.prevent="navigateOptions('prev')"
           @keydown.native.enter.prevent="selectOption"
           @keydown.native.esc.stop.prevent="visible = false"
           @keydown.native.tab="visible = false"
           @paste.native="debouncedOnInputChange"
           @mouseenter.native="inputHovering = true"
           @mouseleave.native="inputHovering = false"
       >
           <template slot="prefix" v-if="$slots.prefix">
               <slot name="prefix"></slot>
           </template>
           <template slot="suffix">
               <i v-show="!showClose" :class="['el-select__caret', 'el-input__icon', 'el-icon-' + iconClass]"></i>
               <i v-if="showClose" class="el-select__caret el-input__icon el-icon-circle-close" @click="handleClearClick"></i>
           </template>
       </el-input>

       <transition name="el-zoom-in-top" @before-enter="handleMenuEnter" @after-leave="doDestroy">
           <el-select-menu ref="popper" :append-to-body="popperAppendToBody" v-show="visible && emptyText !== false">
               <el-scrollbar
                   tag="ul"
                   wrap-class="el-select-dropdown__wrap"
                   view-class="el-select-dropdown__list"
                   ref="scrollbar"
                   :class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }"
                   v-show="options.length > 0 && !loading"
               >
                   <el-option :value="query" created v-if="showNewOption"> </el-option>
                   <slot></slot>
               </el-scrollbar>
               <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0))">
                   <slot name="empty" v-if="$slots.empty"></slot>
                   <p class="el-select-dropdown__empty" v-else>
                       {{ emptyText }}
                   </p>
               </template>
           </el-select-menu>
       </transition>

复制代码

基础用法中有哪些动作和效果!

  • 首先呢 肯定是点击input框时的动作了 这里的话我没看到click事件,那么的话 element是用focus事件代替了click事件了
    // 手动聚焦的方法
    handleFocus(event) {
        if (!this.softFocus) {
        // 判断是否设置了(对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单)automatic-dropdown属性
        // 或者是否可搜索(filterable)
            if (this.automaticDropdown || this.filterable) {
                // visible为true  显示聚焦的样式 并且展示下拉组件
                this.visible = true; 
                if (this.filterable) {
                    // 菜单焦点为ture
                    this.menuVisibleOnFocus = true;
                }
            }
            // 传递给用户对应的focus事件
            this.$emit("focus", event);
        } else {
            this.softFocus = false;
        }
    },
复制代码
  • 回头来看一下 下拉组件的Dom

<el-select-menu ref="popper" :append-to-body="popperAppendToBody" v-show="visible && emptyText !== false">
    <el-scrollbar
        tag="ul"
        wrap-class="el-select-dropdown__wrap"
        view-class="el-select-dropdown__list"
        ref="scrollbar"
        :class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }"
        v-show="options.length > 0 && !loading"
    >
        <el-option :value="query" created v-if="showNewOption"> </el-option>
        <slot></slot>
    </el-scrollbar>
    <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0))">
        <slot name="empty" v-if="$slots.empty"></slot>
        <p class="el-select-dropdown__empty" v-else>
            {{ emptyText }}
        </p>
    </template>
</el-select-menu>
复制代码
  • 这里的 el-select-menu组件代码比较简单
<div class="el-select-dropdown el-popper" :class="[{ 'is-multiple': $parent.multiple }, 
popperClass]" :style="{ minWidth: minWidth }">
        <slot></slot>
</div>
复制代码

可以看到这是下拉框的整体的结构 然后剩下的所有内容都是通过slot来展示 然后被添加到 body 节点上了,通过 position 定位到了输入框的上方或者下方,并且可以根据输入框的位置进行调整。这里element通过很多的第三方库来实现了一部分功能。第三方库又是套用的关系,在el-select-menu中可以看见使用了 vue-popper  而在 vue-popper 中又引入了 popper-manager,同时 vue-popper 又引入了第三方的定位库 popper.js

  `vue-popper`:用于管理组件的弹出框,什么时候创建、在哪个位置创建、什么时候又需要销毁以及怎么销毁
  `popup`:主要是做弹出框的打开和关闭操作
  `popup-manager`:用来管理页面中所有的 modal 层
  `popper.js`:第三方库,主要是用来定位弹出框的
复制代码

18b1d0a8759918f0eb5466492e6fa0e0.png

  • 紧接着就是 el-scrollbar 这里的话就自己封装的滚动条组件了

  • 最后最后就只剩下了 el-option 上代码

 <li
    @mouseenter="hoverItem"
    @click.stop="selectOptionClick"
    class="el-select-dropdown__item"
    v-show="visible"
    :class="{
        selected: itemSelected,
        'is-disabled': disabled || groupDisabled || limitReached,
        hover: hover,
    }"
>
    <slot>
        <span>{{ currentLabel }}</span>
    </slot>
</li>
复制代码

这里发现下拉选择是一个个的li 这里ui的话是通过el-scrollbar里面的render函数来进行创作出来的

这里的el-scrollbar 可以单独的拿出来讲

猜你喜欢

转载自juejin.im/post/7016501058847375368