preface
This paper starts with the code structure of SELECT component to understand how to organize parent-child component communication, and focuses on the analysis of the clever implementation of ElSelect component and ElOption component. It also describes how the four functions of the SELECT component are implemented.
The code structure
Element uses a div to simulate select. The select component contains navigation-mixin.js, option-group.vue, option.vue, select-dropdown.vue, select.vue and other files
The HTML structure of the select. Vue main file is similar to that of the main simulation select, except that Element is more complex. The HTML structure of the SELECT component is as follows (some code is omitted for clarity) :
<div class="el-select">
<! -- Multiple choices -->
<div v-if="multiple" class="el-select__tags">
<span>
<! -- Place the selected tag in multiple selection, display it as a tag, or combine it into a paragraph of text -->
</span>
<! -- Search function -->
<input v-model="query" v-if="filterable" />
</div>
<! -- The selected value is displayed when the option is selected -->
<el-input v-model="selectedLabel" :class="{ 'is-focus': visible }">
<! -- xxx -->
</el-input>
<! -- Drop down box -->
<el-select-menu>
<el-scrollbar v-show="options.length > 0 && ! loading">
<! -- Option content -->
<el-option :value="query" created v-if="showNewOption"> </el-option>
<slot></slot>
</el-scrollbar>
<! -- default text with options blank or select loaded -->
<template
v-if="emptyText && (! allowCreate || loading || (allowCreate && options.length === 0 ))"
>
<! -- xxx -->
</template>
</el-select-menu>
</div>
Copy the code
How do you organize parent and child components
When we looked at the structure of the code in the last section, you might have thought that the SELECT component was complicated. How does it handle the communication between parent and child components? I have analyzed the organization method of its Table component before, using simple store mode, and select component using broadcast/dispatch and Inject /provide. (PS: Broadcast is written by ElementUI itself, vue 2.0 has removed $dispatch and $broadcast)
Inject and dojo.provide
Vue2.2.0 new API, this pair of options need to be used together to allow an ancestor component to inject a dependency to all of its descendants, regardless of component level, for as long as the upstream and downstream relationship is established. In a nutshell: Ancestor components provide variables through providers, and descendant components inject variables through inject.
I’ll look at how ElementUI uses inject and provide, and why this.$parent and this.$children can’t communicate with each other
Dispatch and broadcast
Broadcast code is placed in ‘elder-UI/SRC /mixins/ Emitter ‘, and mixed with mixins, using eg:this.broadcast(‘ElOption’, ‘queryChange’, ”); And this dispatch (” ElSelect “, “setSelected”);
As a little over a year old front end, I have never used broadcast. See Vue$Dispatch and $Broadcast for details
Although broadcast has its drawbacks, for example, the component tree-based event flow approach is rather confusing and does not solve the communication problem between sibling components. But in parent-child nested components, it’s neat to use $dispatch and $broadcast to call events remotely to a parent or child, avoiding the need to pass props or use refs to call component instance methods.
ElSelect and ElOption
If I were to write this component, I might use
<el-select v-model="input" filterable clearable placeholder="Please select">
<el-option label="foo" value="foo"></el-option>
<el-option label="foo" value="foo"></el-option>
</el-select>
Copy the code
There is a default slot in the select component, which is fine, but considering that the Select component needs to communicate with the Option component, for example, the Option component needs to know whether the select component has multiple selections, whether it can search, and so on, The select component needs to know the number of options components and so on. How do you do that? The answer is inject and provide
The option component uses this. Select to modify the parent component’s properties directly:
provide() {
return {
'select': this
};
}
Copy the code
Create as many instances of El-Option as there are options, and look at the created life cycle of El-Option:
created() {
// Push the el-Option instance into the options of the parent component select
this.select.options.push(this);
this.select.cachedOptions.push(this);
this.select.optionsCount++;
this.select.filteredOptionsCount++;
// Listen for custom events
this.$on('queryChange'.this.queryChange);
this.$on('handleGroupDisabled'.this.handleGroupDisabled);
}
Copy the code
This.select.options.push (this); Select. Options has a lot of values. Don’t worry, look at the beforeDestroy life cycle of El-Option
beforeDestroy() {
this.select.onOptionDestroy(this.select.options.indexOf(this));
}
Copy the code
The onOptionDestroy method of the parent component select is destroyed by el-Option and removed from the options of the parent component:
onOptionDestroy(index) {
if (index > - 1) {
this.optionsCount--;
this.filteredOptionsCount--;
// cachedOptions is not removed
this.options.splice(index, 1); }}Copy the code
function
Alternatives are displayed in groups
The HTML structure of option-group.vue is very simple:
<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>
Copy the code
$parent = this.$parent = this.$parent = this.$parent = this. It is appropriate to use inject and provide to be compatible with both cases where el-Option is a child component or a grandchild component.
Local search
It’s actually local filtering, so to enable local search you need to pass in the Filterable field instead of the searchable field, and filtering is really nice. As I said before, you can create as many instances of el-Option as you want, so when you enable filtering, Simply hide the el-Option instances that do not match the re. The code looks like this (simpler than you think) :
queryChange(query) {
// Since select can also create entries manually, manually created entries must be displayed
this.visible =
new RegExp(escapeRegexpString(query), 'i').test(this.currentLabel) ||
this.created;
if (!this.visible) {
// The number of filtered options is reduced by one
this.select.filteredOptionsCount--; }}Copy the code
The remote search
That’s the simple part of the code, because we know that the remote search, the options are returned by the server, just create a new el-Option component.
if (this.remote && typeof this.remoteMethod === 'function') {
this.hoverIndex = - 1;
this.remoteMethod(val); }}Copy the code
Select The remote search component command output
Element-ui When your options are fixed, it will display the corresponding label based on the selected value, but the remote search component is a problem because options are not fixed.
The solution is to pass in options for the selected value. For example, if I have a component ArticleSelect and I select id [1,2], the component will not echo if I don’t do anything about it. Only one, two tags are displayed dryly. But I can echo the title by passing the options of the selected value (for example, [{value:1,label:’ first ‘},{value:2,label:’ second ‘}]) into the component.
The select component searches for options dynamically based on the search term. Let’s look at the code for the ElementUI Select component to set the selected value:
setSelected() {
// omit code for cases that are not multiple choices
/ / multi-select
let result = [];
if (Array.isArray(this.value)) {
this.value.forEach(value= > {
// Note that this is a push operation and the getOption is fetched from cachedOptions (cachedOptions are cached and will not be destroyed because of el-Option destruction)
result.push(this.getOption(value));
});
}
this.selected = result;
// Recalculate the height of the options box after setting
this.$nextTick((a)= > {
this.resetInputHeight();
});
}
Copy the code
As can be seen from the code, the selected value set by Element is a push operation, so subsequent changes of options will not affect the selected value, which perfectly meets my needs
Create entries
Here, more subtly, LET me show the code structure of the drop-down box:
<el-scrollbar v-show="options.length > 0 && ! loading">
<! -- Option content -->
<! ShowNewOption --> showNewOption
<el-option :value="query" created v-if="showNewOption"> </el-option>
<slot></slot>
</el-scrollbar>
Copy the code
The code to calculate the property showNewOption:
showNewOption() {
let hasExistingOption = this.options.filter(option= >! option.created) .some(option= > option.currentLabel === this.query);
// When this.query is null, the el-Option component is destroyed because v-if is the true conditional rendering.
return this.filterable && this.allowCreate && this.query ! = =' ' && !hasExistingOption;
}
Copy the code
conclusion
Element’s SELECT component is a bit complicated, but the functionality, especially the code structure, is really neat. As for the CSS part, not my strengths, I will not analyze.
Refer to the article
- Vue $dispatch and $broadcast details
- There are six ways to communicate between Vue components
- vm.$on( event, callback )
- Element source code analysis series 7-Select(drop down Select box)