[Анализ исходного кода] Выберите компонент ElementUI

внешний интерфейс Element

предисловие

Эта статья начинается со структуры кода компонента select, рассказывается, как компонент select организует связь между родительским и дочерним компонентами, и фокусируется на гениальной реализации компонентов ElSelect и ElOption. И познакомьтесь с тем, как реализованы четыре функции компонента select.

структура кода

Элемент использует div для имитации выбора, а компонент выбора содержитnavigation-mixin.js,option-group.vue,option.vue,select-dropdown.vue,select.vueи т.д. документы

Этоselect.vueСтруктура HTML основного файла аналогична структуре основного аналога select, но Element немного сложнее.Структура HTML компонента select выглядит следующим образом (для ясности некоторые коды опущены):

<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 очень сложен.Как он обрабатывает связь между родительским и дочерним компонентами? Прежде чем я проанализировал метод организации компонента «Таблица», я использовал простойрежим хранения, компонент select использует широковещательную рассылку/отправку и внедрение/предоставление. (PS: трансляцию пишет ElementUI, vue 2.0 удален$dispatch 和 $broadcast)

вводить и обеспечивать

Vue2.2.0 добавляет API, эту пару опций необходимо использовать вместе, чтобы позволить компоненту-предку внедрить зависимость во всех своих потомков, независимо от того, насколько глубоким является уровень компонента, и это всегда будет действовать, когда восходящий и нисходящий потоки отношения устанавливаются. В двух словах: предоставьте переменные в компонентах-предках через провайдера и вставьте переменные в компоненты-потомки через inject.

Позже я разберу, как ElementUI использует inject и provider, и почему их нельзя использовать.this.$parentа такжеthis.$childrenобщаться друг с другом

рассылка и рассылка

Код трансляции помещается в 'element-ui/src/mixins/emitter', микшируется через миксины, например:this.broadcast('ElOption', 'queryChange', '');а такжеthis.dispatch("ElSelect", "setSelected");

Как фронтенд, который работает в индустрии всего больше года, я никогда не использовал вещание.Vue$dispatch 和 $broadcastДетальное объяснение

Хотя широковещательная передача имеет свои недостатки, например, метод потока событий, основанный на древовидной структуре компонентов, действительно сложен для понимания и не решает проблему связи между родственными компонентами. Но во вложенных компонентах родитель-потомок,$dispatch 和 $broadcastПрямой удаленный вызов событий родительскому или дочернему компоненту, избегая операции вызова методов экземпляра компонента путем передачи свойств или использования ссылок, по-прежнему очень лаконичен.

ElSelect и ElOption

Если я напишу этот компонент, его можно будет использовать так<my-select v-model="input" options="options" />, то есть опции компонента 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>

Из структуры кода мы также можем видеть, что в компоненте выбора есть слот по умолчанию, и нет проблем с отображением, но, учитывая, что компоненту выбора необходимо взаимодействовать с компонентом выбора, например, компоненту выбора требуется чтобы узнать, есть ли у компонента выбора несколько вариантов выбора, можно ли его искать и т. д. Компонент выбора должен знать количество компонентов параметров и т. д. Как это сделать? Ответ ввести и предоставить

Непосредственно поместите свой собственный экземпляр в выбранный компонентthisВведенный в компонент опции, компонент опции проходитthis.selectИзмените свойства родительского компонента напрямую:

provide() {
  return {
    'select': this
  };
}

Создайте столько вариантов, сколько у вас есть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));
}

выбор родительского компонентаonOptionDestroyметод,el-optionУничтоженный, он также будет удален из опций родительского компонента:

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связь, для совместимостиel-optionДля двух случаев дочернего компонента или внучатого компонента используйтеinjectа такжеprovideпросто подходит.

Локальный поиск

По сути, это локальная функция фильтрации, поэтому для включения функции локального поиска нужно пройти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);
  }
}

выберите эхо компонента удаленного поиска

element-ui Когда ваш параметр исправлен, он будет отображать соответствующую метку на основе выбранного вами значения, но компонент удаленного поиска связан сoptionsНе исправлено, проблема с эхом.

Решение состоит в том, чтобы передать выбранное значениеoptionsВходящий, скажем, у меня есть компонентArticleSelect, значение идентификатора, которое я выбрал, равно [ 1,2 ], если оно не обработано, этот компонент не будет отображаться. Сухо отображаются только два тега 1 и 2. Но я могу получить выбранное значение, поставивoptions(например, значение[{value:1,label:'第一篇'},{value:2,label:'第二篇'}]), переданный в этот компонент, для достижения эхо-заголовка.

Однако некоторые люди могут спросить, не будут ли параметры удаленного поиска компонента select динамически изменяться с ключевыми словами поиска, почему это возможно? Давайте посмотрим на код компонента выбора ElementUI для установки выбранного значения:

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();
    });
  }

Из кода видно, что выбранное значение настройки Элемента является операцией push, поэтому последующие изменения опций не повлияют на выбранное мной значение, которое отлично решает мои потребности.

Создать запись

Вот поумнее, покажу структуру кода выпадающего окна:

<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为空的时候,因为v-if是真正的条件渲染,所以这个el-option组件又被销毁了,(没有手动去销毁哦,是不是很巧妙)
  return this.filterable && this.allowCreate && this.query !== '' && !hasExistingOption;
}

Суммировать

Компонент select в Element немного сложен, но реализация функции, особенно идея структуры кода, действительно сложна. Что касается части CSS, я не буду разбирать ее, если это не моя сила.

Справочная статья