内容简介:Element 使用 div 模拟了 select,select 组件含有其上一节分析代码结构时候,读者可能就觉得 select 组件很复杂了,其是如何处理父子组件的通信的呢?之前我分析过其 Table 组件的组织方法,使用了简单的store 模式,select 组件则采用了 broadcast/dispatch 和 inject/provide。(PS:broadcast 是 Element 的自己写的,vue 2.0 已经移除了
Element 使用 div 模拟了 select,select 组件含有 navigation-mixin.js
, option-group.vue
, option.vue
, select-dropdown.vue
, select.vue
等文件
其 select.vue
主文件的 HTML 结构和主流模拟 select 的思路相似,只不过 Element 更复杂一点,select 组件 HTML 结构如下:
<div class="el-select"> <!-- 多选的情况 --> <div v-if="multiple" class="el-select__tags"> <span> <!-- 放置多选时的选中的tag,以tag展现,或者合并成一段文字 --> </span> <!-- 搜索功能 --> <input v-model="query" v-if="filterable" /> </div> <!-- 单选的情况 --> <el-input v-model="selectedLabel" :class="{ 'is-focus': visible }"> <!-- xxx --> </el-input> <!--下拉框--> <el-select-menu> <el-scrollbar v-show="options.length > 0 && !loading"> <!-- 选项内容 --> <el-option :value="query" created v-if="showNewOption"> </el-option> <slot></slot> </el-scrollbar> <!-- options为空显示的默认文字或者select处于loading的情况 --> <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0 ))" > <!-- xxx --> </template> </el-select-menu> </div> 复制代码
如何组织父组件和子组件
上一节分析代码结构时候,读者可能就觉得 select 组件很复杂了,其是如何处理父子组件的通信的呢?之前我分析过其 Table 组件的组织方法,使用了简单的store 模式,select 组件则采用了 broadcast/dispatch 和 inject/provide。(PS:broadcast 是 Element 的自己写的,vue 2.0 已经移除了
$dispatch 和 $broadcast
)
inject 和 provide
Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
后面我会分析 ElementUI 如何使用的 inject 和 provide,以及为什么不能使用 this.$parent
dispatch 和 broadcast
broadcast 的代码放置在'element-ui/src/mixins/emitter',通过 mixins 混入,使用方式 eg: this.broadcast('ElOption', 'queryChange', '');
和 this.dispatch("ElSelect", "setSelected");
作为一个才入行一年多的前端,真没用过 broadcast ,具体详见
Vue $dispatch 和 $broadcast
详解
虽然 broadcast 有它的缺点,比如基于组件树结构的事件流方式实在是让人难以理解,没有解决兄弟组件间的通信问题。但是在父子层嵌套组件中,通过 $dispatch 和 $broadcast
定向的向某个父或者子组件远程调用事件,避免了通过传 props 或者使用 refs 调用组件实例方法的操作,还是很简洁的。
ElSelect 和 ElOption
如果我写这个组件,可能会这么使用 ,即 Select 组件的选项通过 options 传入组件。也许最后功能可以实现,但是这样做感觉有点封装过度了。从这个组件的结构来讲,和原生的select不太像。我们经常这样使用 Select 组件,是不是很直观:
<el-select v-model="input" filterable clearable placeholder="请选择"> <el-option label="foo" value="foo"></el-option> <el-option label="foo" value="foo"></el-option> </el-select> 复制代码
从代码结构也可以看到 select 组件里有个默认插槽,显示是没什么问题,但是 select 组件需要和 option 组件互相通信,比如 option 组件需要了解 select 组件是否多选,是否可以搜索等等,select 组件需要了解 option 组件的个数等等,如何做到呢?答案就是 inject 和 provide
select 组件里直接把自己注入到了 option 组件,option 组件通过 this.select 直接修改父组件属性:
provide() { return { 'select': this }; } 复制代码
有多少 option 就创建多少 el-option
实例,看一下 el-option
的 created
的生命周期:
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-option
的 beforeDestroy
生命周期
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 的结构很简单:
<ul class="el-select-group__wrap" v-show="visible"> <li class="el-select-group__title">{{ label }}</li> <li> <ul class="el-select-group"> <slot></slot> </ul> </li> </ul> 复制代码
分组功能相当于把 el-option 包了一层,那么这个时候 el-option
组件就相当于 el-select
组件的孙组件了,那么 el-option 和 el-select 组件就不能通过 this.$parent
通信了,是不是更理解 provide/inject 的作用了。
本地搜索
其实是本地筛选功能,因此启用本地搜索功能需要传入 filterable
字段而不是 searchable
,筛选功能真的很妙,前面也说过有多少 option 就创建多少 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--; } } 复制代码
远程搜索
代码部分就是如此简单,因为我们知道远程搜索,options 都是服务端返回,重新新建 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 后续改变也不会影响我选中的值,完美解决了我的需求
创建条目
这里就更巧妙了,我再把下拉框的代码结构展示一下:
<el-scrollbar v-show="options.length > 0 && !loading"> <!-- 选项内容 --> <!-- 看到这个受 showNewOption 控制的option了嘛,他就是用来创建option --> <el-option :value="query" created v-if="showNewOption"> </el-option> <slot></slot> </el-scrollbar> 复制代码
计算属性 showNewOption
的代码:
showNewOption() { let hasExistingOption = this.options.filter(option => !option.created) .some(option => option.currentLabel === this.query); // this.query为空的时候,这个el-option组件又被销毁了(没有手动去做哦,是不是很巧妙) return this.filterable && this.allowCreate && this.query !== '' && !hasExistingOption; } 复制代码
总结
Element 的 select 组件有点复杂,但是功能实现,尤其是代码结构的构思上真的非常精巧。至于 CSS 部分,非我强项,就不分析了。
参考文章
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- React Hooks 源码解析(一):类组件、函数组件、纯组件
- 权限组件的源码分析
- OpenFalcon 源码分析(Nodata组件)
- 【React源码解读】- 组件的实现
- 深入剖析Vue源码 - 组件基础
- 深入剖析Vue源码 - 组件进阶
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
你的品牌,价值千万
温迪 / 人民邮电出版社 / 2018-7-1 / 49.00元
“大道无术,万法归心。” 不管是互联网、社交媒体,还是 AI 怎样让人眼花缭乱。从“真心”出发塑造的个人品牌,都将带你从容面对任何一种变化的冲击。现代生活变得越来越透明,如果你不懂得如何真实、精准地定位和呈现自己,你的个人品牌在 碎片信息中被误解、被曲解就是一种必然。 本书分四步引导你剖析自己、发现自我,构建可持续的品牌生态系统,策划品牌战略,提升个人呈现力,并在最后带你勾画出一幅完整的个人......一起来看看 《你的品牌,价值千万》 这本书的介绍吧!