Element源码分析系列8-Cascader(级联选择器)

本文详细分析了Element UI中Cascader(级联选择器)组件的源码,从输入框的HTML结构、代码实现到下拉菜单的展现逻辑。讨论了如何通过点击和键盘事件控制下拉菜单的显示与隐藏,以及搜索功能的实现,特别是防抖处理和数据结构的操作。还探讨了级联选择器下拉菜单的挂载、定位和动画效果,以及与输入框的交互。通过对渲染列表和事件处理的分析,揭示了组件内部的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

级联选择器,如下图,也是一种常用的组件,这个组件会比较复杂一点

Element中和该组件相关的文件有 main.vuemenu.vue2个文件,前者代表输入框部分,后者代表下方的级联选择部分,以及附加的js文件popper.js以及vue.popper.js,用来处理弹出框逻辑,前面文章介绍过,这4个文件总代码量2000行左右,首先要明确,Element中把弹出框的逻辑分离出去了,放在专门的popper.js中,因为许多组件都要用到该弹出框。该组件官网代码 点此

级联选择器输入框的html结构

先来看main.vue中的html结构,main.vue代表输入框部分,简化后的html结构如下

<span class="el-cascader">
    <el-input>
        <template slot="suffix">
            <i v-if></i>
            <i v-else></i>
        </template>
    </el-input>
    <span class="el-cascader__label">
        ...
    </span>
</span>
复制代码

结构很简单,最外层一个span包裹所有元素,该span相对定位且inline-block,里面是一个<el-input>输入框组件,该输入框用来搜索目标内容(内容就是级联选择器的data),然后<el-input>里面一个template作为插槽存放了2个i标签,注意这里的slot="suffix"这是将这2个i标签作为具名插槽的内容插入<el-input>中的对应位置,带表了下箭头和清空输入框的按钮。然后是一个span,这个span就是下图中输入框内的文字

注意这里输入框的文字不是直接作为value放在输入框内的,而是一个绝对定位的span放在输入框上,一般我们会直接把选中的文字作为输入框的value填充,但这里没有这么做,因为后面有个搜索功能,需要在输入框内输入文字

是不是没有发现下拉菜单的html结构?因为下拉菜单是挂载在document.body上的,通过popper.js来控制,所以结构被分离出去了

级联选择器输入框的代码分析

先来看最外层span的代码

<span
    class="el-cascader"
    :class="[
      {
        'is-opened': menuVisible,
        'is-disabled': cascaderDisabled
      },
      cascaderSize ? 'el-cascader--' + cascaderSize : ''
    ]"
    @click="handleClick"
    @mouseenter="inputHover = true"
    @focus="inputHover = true"
    @mouseleave="inputHover = false"
    @blur="inputHover = false"
    ref="reference"
    v-clickoutside="handleClickoutside"
    @keydown="handleKeydown"
  >
复制代码

作为组件最外层的span,其功能主要就是点击之后会弹出/隐藏下拉框,前面class部分就是控制该输入框是否禁用的样式,is-opened这个类很奇怪,源码里没有,而且审查元素也发现该类是空,menuVisible是组件内data中的变量,控制是否显示下拉菜单,自然可以想到,下面的@click="handleClick"中有控制该变量的代码,该方法如下

handleClick() {
      if (this.cascaderDisabled) return;
      this.$refs.input.focus();
      if (this.filterable) {
        this.menuVisible = true;
        return;
      }
      this.menuVisible = !this.menuVisible;
    },
复制代码

首先判断组件是否禁用,如果禁用则直接返回,第二句this.$refs.input.focus()是获取到该组件内的<el-input>并让其获得焦点,focus是原生用法,注意这里默认状态下组件内的输入框是readonly只读的,只有在开启了搜索状态下才能获得焦点,而开启搜索由filterable这个prop控制,用户传入,<el-input>:readonly="readonly"这句话就是控制只读的,readonly是个计算属性,如下

readonly() {
      const isIE = !this.$isServer && !isNaN(Number(document.documentMode));
      return !this.filterable || (!isIE && !this.menuVisible);
    }
复制代码

这里首先判断是不是ie浏览器,首先判断是不是服务端渲染,如果是则直接返回false,然后这句话!isNaN(Number(document.documentMode)就可以很轻松的判断是否是ie,之前我记得一般是用navigator.userAgent.indexOf("MSIE")>0来判断的,documentMode是一个ie特有属性

ie返回一个数字,其他浏览器返回undefined,则Numer(undefined)就是NaN,那为啥不直接用 document.documentMode!==undefined来判断呢这里不明白,难道是怕undefined不是真正的undefined?因为undefined可以被修改。继续看return逻辑,如果是开启搜索状态(filterable为true,那么一般情况下输入框readonly
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值