博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【源码浅析】 ElementUI 的 Select 组件
阅读量:6113 次
发布时间:2019-06-21

本文共 4845 字,大约阅读时间需要 16 分钟。

代码结构

Element 使用 div 模拟了 select,select 组件含有navigation-mixin.jsoption-group.vueoption.vueselect-dropdown.vueselect.vue等文件

select.vue主文件的 HTML 结构和主流模拟 select 的思路相似,只不过 Element 更复杂一点,select 组件 HTML 结构如下(为了结构清晰,部分代码省略):

复制代码

如何组织父组件和子组件

上一节分析代码结构时候,读者可能就觉得 select 组件很复杂了,其是如何处理父子组件的通信的呢?之前我分析过其 Table 组件的组织方法,使用了简单的 ,select 组件则采用了 broadcast/dispatch 和 inject/provide。(PS:broadcast 是 ElementUI 的自己写的,vue 2.0 已经移除了)

inject 和 provide

Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。

后面我会分析 ElementUI 如何使用的 inject 和 provide,以及为什么不能使用 this.$parentthis.$children来互相通信

dispatch 和 broadcast

broadcast 的代码放置在'element-ui/src/mixins/emitter',通过 mixins 混入,使用方式 eg:this.broadcast('ElOption', 'queryChange', '');this.dispatch("ElSelect", "setSelected");

作为一个才入行一年多的前端,真没用过 broadcast ,具体详见

虽然 broadcast 有它的缺点,比如基于组件树结构的事件流方式实在是让人难以理解,没有解决兄弟组件间的通信问题。但是在父子层嵌套组件中,通过 $dispatch 和 $broadcast 定向的向某个父或者子组件远程调用事件,避免了通过传 props 或者使用 refs 调用组件实例方法的操作,还是很简洁的。

ElSelect 和 ElOption

如果我写这个组件,可能会这么使用 <my-select v-model="input" options="options" />,即 Select 组件的选项通过 options 传入组件。也许最后功能可以实现,但是这样做感觉有点封装过度了。从这个组件的结构来讲,和原生的不太像。我们经常这样使用 Select 组件,是不是很直观:

复制代码

从代码结构也可以看到 select 组件里有个默认插槽,显示是没什么问题,但是考虑到 select 组件需要和 option 组件互相通信,比如 option 组件需要了解 select 组件是否多选,是否可以搜索等等,select 组件需要了解 option 组件的个数等等,如何做到呢?答案就是 inject 和 provide

select 组件里直接把自己的实例this注入到了 option 组件,option组件通过 this.select 直接修改父组件属性:

provide() {  return {    'select': this  };}复制代码

有多少选项就创建多少 el-option实例,看一下el-optioncreated的生命周期:

created() {  //把 el-option实例push进父组件select的options  this.select.options.push(this);  this.select.cachedOptions.push(this);  this.select.optionsCount++;  this.select.filteredOptionsCount++;  // 监听自定义的事件  this.$on('queryChange', this.queryChange);  this.$on('handleGroupDisabled', this.handleGroupDisabled);}复制代码

有同学可能就问了,this.select.options.push(this);怎么一直是 push 操作,如果我 选项不定,select.options岂不是有很多值。不用担忧,看一下el-optionbeforeDestroy 生命周期

beforeDestroy() {  this.select.onOptionDestroy(this.select.options.indexOf(this));}复制代码

父组件 select 的 onOptionDestroy 方法,el-option销毁了,父组件的options里面也会将其移除:

onOptionDestroy(index) {  if (index > -1) {      this.optionsCount--;      this.filteredOptionsCount--;      // cachedOptions 没有去除      this.options.splice(index, 1);  }}复制代码

功能

备选项分组展示

option-group.vue里其 HTML 的结构很简单:

  • {
    { label }}
复制代码

分组功能相当于把 el-option 包了一层,那么这个时候el-option组件就相当于el-select组件的孙组件了,那么 el-option 和 el-select 组件就不能通过this.$parent通信了,为了兼容el-option为子组件或为孙组件的两种情况,使用injectprovide就很合适。

本地搜索

其实是本地筛选功能,因此启用本地搜索功能需要传入 filterable字段而不是searchable,筛选功能真的很妙,前面也说过有多少选项就创建多少 el-option实例,因此当我们启用筛选得时候,只需要把和正则不匹配的 el-option 实例隐藏掉就可以了。代码如下(是不是比想象中的简单):

queryChange(query) {   // 因为select还可以手动创建条目,所以手动创建的条目一定显示  this.visible =    new RegExp(escapeRegexpString(query), 'i').test(this.currentLabel) ||    this.created;  if (!this.visible) {    // 筛选的选项数目减一    this.select.filteredOptionsCount--;  }}复制代码

远程搜索

代码部分就是如此简单,因为我们知道远程搜索,选项都是服务端返回,重新新建el-option组件即可。

if (this.remote && typeof this.remoteMethod === 'function') {  this.hoverIndex = -1;        this.remoteMethod(val);  }}复制代码

select 远程搜索组件回显

element-ui 当你的选项是固定的时候,它会基于你选中的 value,回显对应的 label,但是远程搜索组件由于options不固定,回显就是一个问题。

解决的方法就是传入已选中的值的options传入,比如我有一个组件ArticleSelect ,我选中的 id 值为 [ 1,2 ] ,如果不做处理的话,这个组件就不会回显。仅干巴巴的显示 1,2 两个 tag。但是我可以通过把选中的值的 options(比如值为[{value:1,label:'第一篇'},{value:2,label:'第二篇'}]) 传入这个组件,实现回显显示标题。

但,可能有人就问了,select 组件远程搜索 options 不是会随着搜索的关键词而动态变化么,为什么这样可以?我们看一下 ElementUI select 组件设置选中值的代码:

setSelected() {    // 省略不是多选的情况的代码    // 多选    let result = [];    if (Array.isArray(this.value)) {      this.value.forEach(value => {        // 注意到这里是push操作,且getOption是从cachedOptions里面取的,(cachedOptions是被缓存的,不会因为el-option销毁而销毁)        result.push(this.getOption(value));      });    }    this.selected = result;    // 设置完成之后重新计算选项框的高度    this.$nextTick(() => {      this.resetInputHeight();    });  }复制代码

由代码可知, Element 设置 选中的值是一个 push 操作,所以 options 后续改变也不会影响我选中的值,完美解决了我的需求

创建条目

这里就更巧妙了,我再把下拉框的代码结构展示一下:

复制代码

计算属性showNewOption的代码:

showNewOption() {  let hasExistingOption = this.options.filter(option => !option.created)    .some(option => option.currentLabel === this.query);    // this.query为空的时候,因为v-if是真正的条件渲染,所以这个el-option组件又被销毁了,(没有手动去销毁哦,是不是很巧妙)  return this.filterable && this.allowCreate && this.query !== '' && !hasExistingOption;}复制代码

总结

Element 的 select 组件有点复杂,但是功能实现,尤其是代码结构的构思上真的非常精巧。至于 CSS 部分,非我强项,就不分析了。

参考文章

转载于:https://juejin.im/post/5d01fce16fb9a07eb3097548

你可能感兴趣的文章
JPGraph
查看>>
实验二 Java面向对象程序设计
查看>>
------__________________________9余数定理-__________ 1163______________
查看>>
webapp返回上一页 处理
查看>>
新安装的WAMP中phpmyadmin的密码问题
查看>>
20172303 2017-2018-2 《程序设计与数据结构》第5周学习总结
查看>>
eclipse中将一个项目作为library导入另一个项目中
查看>>
Go语言学习(五)----- 数组
查看>>
Android源码学习之观察者模式应用
查看>>
Content Provider的权限
查看>>
416. Partition Equal Subset Sum
查看>>
centos7.0 64位系统安装 nginx
查看>>
数据库运维平台~自动化上线审核需求
查看>>
注解开发
查看>>
如何用 Robotframework 来编写优秀的测试用例
查看>>
Django之FBV与CBV
查看>>
Vue之项目搭建
查看>>
app内部H5测试点总结
查看>>
Docker - 创建支持SSH服务的容器镜像
查看>>
[TC13761]Mutalisk
查看>>