Сверхдетальный анализ исходного кода ElementUI — выбор (шаблон)

Vue.js
Сверхдетальный анализ исходного кода ElementUI — выбор (шаблон)

предисловие

Далее следует очень болезненное время! !

Сегодня я подробно проанализировал исходный код Select.Когда я увидел, что в исходном коде было 900 строк, я был совершенно ошарашен.Это исходный код, который я читал больше всего.Посмотрев его, я обнаружил, что есть в ней много знаний.Точка, есть 16 импортируемых модулей, которые содержат различные компоненты, миксины и служебные функции, поэтому ввиду ограниченного объема этой статьи, я планирую написать ее в двух частях, а именно:Шаблоны"а также"Глава метода», глава шаблона в основном анализирует шаблон Select и некоторые относительно простые атрибуты, а глава метода фокусируется на анализеmethodsВ нем есть различные методы.Теперь давайте посмотрим, что делает компонент Select.Рекомендуется поставить лайк/избранное перед просмотром.

Использование и особенности

Чтобы понять компонент пользовательского интерфейса, вы должны сначала начать с его функции.Только после понимания конкретной функции компонента вы поймете, зачем его инкапсулировать? Как упаковать? существуетОфициальная документация ElementUIТам подробно описаны функции и методы, и те, кто не знаком с ним, могут сначала изучить его. Select в основном используется следующими способами:

  • Основное использование: это основной раскрывающийся список, положение раскрывающегося списка будет выборочно отображаться выше или ниже в зависимости от размера пространства на странице.
  • Отключить параметры: вы можете отключить параметр или весь раскрывающийся список.
  • Кнопка очистки: Излишне говорить, что компонент ввода также имеет эту функцию.
  • Множественный выбор: выберите несколько раскрывающихся параметров, выбранные параметры могут отображаться в виде тегов или объединяться в абзац.
  • Пользовательские параметры раскрывающегося списка: параметры раскрывающегося списка могут быть пользовательскими стилями.
  • Группировка: выпадающие параметры могут быть сгруппированы по определенным категориям
  • Поиск: объедините с сервером, чтобы создать поисковые ассоциации.
  • Создать запись: вы можете создать тег, набрав и отобразив его в поле ввода.

Видно, что функций Select очень много, и в основном учитываются все функции, которые нам нужно использовать в наших повседневных нуждах.Так как функций так много, инкапсулировать их должно быть очень хлопотно. все, 900 строк кода Написаны не зря! !

После уточнения функции нам очень полезно посмотреть исходный код, мы можем найти соответствующую реализацию кода по функции, а затем перейти непосредственно к исходному коду:

Базовая структура

el-select

Лучше не смотреть на структуру непосредственно в исходном коде, легко запутаться.Сначала давайте посмотрим на структуру HTML, отображаемую при самом простом использовании:

Из этой относительно простой структуры легко увидеть, что основная часть состоит из входных и суффиксных элементов.Теперь давайте по крупицам проанализируем структуру шаблона:

Во-первых, самый внешний уровень — это класс с именемel-selectизdiv, если объявленоsize, также согласноsizeДобавить кel-select-sizeкласс, который обертываетel-select__tagsизdiv, мы не будем рассматриватьtagsПохожие, посмотрите сначалаel-input.

этоel-inputРендеринг — это компонент ввода, инкапсулированный ElementUI. Если вы его еще не видели, вы можете сначала двигаться вперед.Сверхдетальный анализ исходного кода ElementUIВнимательно прочитайте входной исходный код.

<el-input>
  <template slot="prefix" v-if="$slots.prefix">
    <slot name="prefix"></slot>
  </template>
  <template slot="suffix">
    <i v-show="!showClose" :class="['el-select__caret', 'el-input__icon', 'el-icon-' + iconClass]"></i>
    <i v-if="showClose" class="el-select__caret el-input__icon el-icon-circle-close" @click="handleClearClick"></i>
  </template>
</el-input>

Компонент ввода содержит два слота, префикс и суффикс.prefixСлот используется для отображения содержимого заголовка компонента Select (если есть) иsuffixОн используется для отображения пустой кнопки и маленькой стрелки за ней. Вы можете видеть, что здесь есть деталь, которая используется для маленьких стрелок.v-showИ кнопка очистки используетсяv-if, вот краткое введение в разницу между ними:

  • Оба работают для переключения отображения и скрытия элементов DOM.
  • v-showУправляет элементами DOMdisplay: noneсвойства, которые не изменяют структуру дерева DOM
  • v-ifУправляет деревом DOM, напрямую добавляя или удаляя контролируемые элементы DOM.
  • v-ifсуществует"Не отображается, когда начальное условие ложно", не начинает рендеринг, пока в первый раз не станет правдой
  • v-ifЕсть высшее"переключение накладных расходов",v-showЕсть высшее"Накладные расходы на начальный рендеринг"
  • то естьv-showОн подходит для частых манипуляций с DOM иv-ifОн используется, когда вы не часто манипулируете его структурой DOM.

События, вызванные им, также зарезервированы для следующего выпуска, чтобы проанализировать его вместе с вами. Не забудьте прочитать его вовремя.

Поскольку последняя структура использует много компонентов, которые ранее не встречались, сначала будут проанализированы компоненты, на которые ссылается Select.

el-select-menu

прежде всегоel-select-menu, посмотрите на импортированные модули, чтобы увидеть, что использованиеselect-dropdown.vueфайл, зайди и посмотри. Структура этого компонента очень проста, есть только одинdiv, который содержит слот, этоdivизclassда"el-select-dropdown el-popper", он отображает на странице следующую структуру:

Вы можете видеть, что это структура раскрывающегося списка, который добавляется к узлу body черезpositionОн располагается над или под полем ввода и может регулироваться в соответствии с положением поля ввода. Сам по себе компонент не очень сложный, но он смешивается сvue-popper, пока вvue-popperповторно представлен вpopper-manager,в то же времяvue-popperПредставлена ​​сторонняя библиотека позиционированияpopper.js, поэтому взаимосвязь здесь очень сложная, см. следующую картинку:

Давайте сначала поговорим о роли каждого модуля:

  • vue-popper: Всплывающее окно для управления компонентами, когда создавать, где создавать, когда уничтожать и как уничтожать
  • popupВ основном для открытия и закрытия всплывающего окна
  • popup-manager: используется для управления всеми модальными слоями на странице.
  • popper.js: сторонняя библиотека, в основном используемая для поиска всплывающего окна.

дляpopper.jsАнализ относится кЭтот блог CSDN

Поскольку содержимое каждого модуля очень много, просто чтобы выбрать анализ и выбирать компоненты, связанные с ним, чтобы увидеть, если конкретно я могу быть местом проведенияgithubПосмотреть выше.

Вернемся снова к компоненту Select,el-select-menuзавернут вel-scrollbarЧтобы содержимое раскрывающегося списка прокручивалось, следующее содержимоеel-scrollbarанализ.

el-scrollbar

Давайте сначала посмотрим на входной файлindex.jsон импортируетScrollBarдаsrc/main,этоel-scrollbarФайл компонента, официалы сказали, что вся идея этого файла для справкиgemini-scrollbar, пошел сравнивать и обнаружил, что идея та же, даже нейминг тот же, но другие совместимы.

Файлы, импортированные сюда, в основном:

  • utils/resize-event.js:resizeПривязка и отвязка событий
  • utils/scrollbar-width.js: Рассчитайте ширину полосы прокрутки
  • toObject: объединить все объекты в массиве в один объект
  • Bar: пользовательский компонент полосы прокрутки

main.js/render

Я проанализировал исходный код каждого файла, сначала посмотрите наmain.jsВнутри исходного кода:

// main.js
render(h) {
  // 获取系统自带的滚动条的宽度
  // scrollbarWidth() 看后文
  let gutter = scrollbarWidth();
  let style = this.wrapStyle;
  // 如果滚动条存在
  if (gutter) {
    // 我觉得这地方应该是 `gutterWidth` 不过不重要了
    const gutterWith = `-${gutter}px`;
    const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;

    if (Array.isArray(this.wrapStyle)) {
      // toObject 看后文
      style = toObject(this.wrapStyle);
      style.marginRight = style.marginBottom = gutterWith;
    } else if (typeof this.wrapStyle === 'string') {
      style += gutterStyle;
    } else {
      style = gutterStyle;
    }
  }
  // 这是最外层的 ul
  const view = h(
    this.tag,
    {
      class: ['el-scrollbar__view', this.viewClass],
      style: this.viewStyle,
      ref: 'resize'
    },
    // 子虚拟节点数组
    this.$slots.default
  );
  // ul 外层包裹的 div
  const wrap = (
    <div
      ref='wrap'
      style={style}
      onScroll={this.handleScroll}
      class={[
        this.wrapClass,
        'el-scrollbar__wrap',
        gutter ? '' : 'el-scrollbar__wrap--hidden-default'
      ]}
    >
      {[view]}
    </div>
  );
  let nodes;
  // 是否使用元素滚动条,默认是 false
  // 使用自定义的 Bar 组件
  if (!this.native) {
    nodes = [
      wrap,
      <Bar move={this.moveX} size={this.sizeWidth}></Bar>,
      <Bar vertical move={this.moveY} size={this.sizeHeight}></Bar>
    ];
  } else {
    nodes = [
      <div
        ref='wrap'
        class={[this.wrapClass, 'el-scrollbar__wrap']}
        style={style}
      >
        {[view]}
      </div>
    ];
  }
  return h('div', { class: 'el-scrollbar' }, nodes);
},

Как видите, в основном используется прокручиваемая часть этого выпадающего списка.renderФункция рендеринга используется для построения DOM-структуры.Вся визуализированная структура показана на рисунке:

оliРендеринг этикетки выполняетсяel-optionГотово, анализировать позже. В функции рендеринга на внешний слойwrapсвязалonscrollсобытие, метод слушателя находится вmethodsОн определяет:

// onscroll 事件处理函数
handleScroll() {
  const wrap = this.wrap;

  // 计算出滚动条需要滚动的距离(百分比)
  this.moveY = (wrap.scrollTop * 100) / wrap.clientHeight;
  this.moveX = (wrap.scrollLeft * 100) / wrap.clientWidth;
},

Когда внутренний список прокручивается, рассчитайте расстояние, на которое должна прокручиваться полоса прокрутки, здесь используется процент, а затем вBar组件里使用。 этоBarЭто официальный компонент пользовательской полосы прокрутки, который также анализируется ниже. Мы замечаем, что компонент получаетnativeАтрибут, этот атрибут указывает, использовать ли полосу прокрутки, которая поставляется с браузером, по умолчаниюfalseТо есть не использовать, а использоватьBarкомпонент, затем поместите всю структуру вhОставьте это Vue для синтаксического анализа.

main.js/methods

methodsОпределены два метода:

  • handleScroll:onscrollобработчик события
  • update: при срабатыванииresizeсобытие, изменить размер полосы прокрутки
update() {
  // 宽高百分比
  let heightPercentage, widthPercentage;
  const wrap = this.wrap;
  if (!wrap) return;

  // 求出可视区域占内容总大小的百分比,这就是滚动条相对于内容的百分比
  heightPercentage = (wrap.clientHeight * 100) / wrap.scrollHeight;
  widthPercentage = (wrap.clientWidth * 100) / wrap.scrollWidth;

  // 滚动条的大小
  // 如果可视区域比内容总大小要小,证明需要滚动,把百分比赋值给 sizeXXX
  // 如果不需要滚动 clientHeight = scrollHeight
  this.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : '';
  this.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : '';
}

Когда компонент раскрывающегося списка смонтирован, вызовитеupdateметод, стоит отметить, что в компонентеpropОдним из свойств являетсяnoresize, это свойство запрещает изменение размера фрейма, официальный комментарий: «еслиcontainerРазмер не изменится, лучше настроить его на оптимизацию производительности». Оптимизированную производительность можно увидеть позже, а компонент вызывается до его монтирования и уничтожения.updateметод, который часто называютupdateБудет потреблять определенное количество производительности, поэтому, когда мы не хотим изменять размер кадра, попробуйте объявитьnoresizeАтрибуты.

mounted() {
  if (this.native) return;
  // update 需要用到更新后的 DOM,所以放在 $nextTick 里
  this.$nextTick(this.update);
  // 如果可以调整框架的大小,就给元素添加一个 resize 监听事件
  !this.noresize && addResizeListener(this.$refs.resize, this.update);
},

beforeDestroy() {
  if (this.native) return;
  // 移除元素的 resize 监听事件
  !this.noresize && removeResizeListener(this.$refs.resize, this.update);
}

оaddResizeListenerСпособ официальный - позаимствовать сторонний пакетresize-observer-polyfillиметь дело сresizeмероприятие,ResizeObserverЭто новый API с очень хорошей производительностью. Перейдите на MDN, чтобы узнать о нем.

Bar

Далее мы видимBarВызов компонента:

<Bar move={this.moveX} size={this.sizeWidth}></Bar>,
<Bar vertical move={this.moveY} size={this.sizeHeight}></Bar>

Передаются два или три аргумента:

  • move: Расстояние для перемещения по горизонтали или вертикали
  • size: размер полосы прокрутки
  • vertical: Является ли это вертикальной полосой прокрутки, а не горизонтальной полосой прокрутки

dom.js/on

bar.jsфайлBarКомпоненты, которые импортируют объекты двух классов инструментов.По поводу анализа классов инструментов я планирую написать специальную колонку позже.Здесь я кратко рассмотрю связанные методы.

/**
 * 封装 on 方法给指定元素绑定事件
 * @param {HTMLElement} element 要绑定事件的元素
 * @param {String} event 要绑定的事件
 * @param {Function} handler 事件触发时执行的函数
 */
export const on = (function() {
  if (!isServer && document.addEventListener) {
    return function(element, event, handler) {
      if (element && event && handler) {
        // false 表示在冒泡阶段执行
        // true 表示在捕获阶段执行
        element.addEventListener(event, handler, false);
      }
    };
  } else {
    // IE 中使用 attachEvent 添加事件监听
    return function(element, event, handler) {
      if (element && event && handler) {
        element.attachEvent('on' + event, handler);
      }
    };
  }
})();

этоonМетод используется для привязки событий к заданному элементу.При внимательном рассмотрении исходного кода обнаруживается, что он использует функцию немедленного выполнения (МИЭФ), а затем экспортирует результат выполнения.Результатом его выполнения по-прежнему является функция . Здесь есть два вопроса:

  • Зачем использовать немедленную функцию?
  • Зачем возвращать функцию?

Давайте поговорим о втором, возврат функции, очевидно, использует "Закрытие", преимущество закрытия заключается в том, что "доступ к внешней области', например здесьisServerто естьdom.jsопределенные в нем переменные. Но использование замыканий приводит к "утечка памяти", если не уничтожить, наша память будет перегружена, поэтому здесь мы используем"выполнить функцию немедленно" для устранения побочных эффектов замыканий,

bar.js/render

возвращайся сноваBarкомпонент, который до сих пор используетсяrenderфункция для рендеринга компонента:

render(h) {
  const { size, move, bar } = this;
  return (
    <div
      class={ ['el-scrollbar__bar', 'is-' + bar.key] }
      onMousedown={ this.clickTrackHandler } >
      <div
        ref="thumb"
        class="el-scrollbar__thumb"
        onMousedown={ this.clickThumbHandler }
        style={ renderThumbStyle({ size, move, bar }) }>
      </div>
    </div>
  );
},

Визуализированную структуру можно увидеть на предыдущем изображении, которое представляет собой два вложенных друг в другаdiv, дваdivвсе связаныonmousedownСобытия, используемые для обработки событий нажатия мыши, вstyleеще одинrenderThumbStyleфункция, давайте сначала посмотрим на функцию этой функции:

export function renderThumbStyle({ move, size, bar }) {
  const style = {};
  // 平移多少距离
  const translate = `translate${bar.axis}(${ move }%)`;

  // 设置滚动条的宽/高
  style[bar.size] = size;
  style.transform = translate;
  style.msTransform = translate;
  style.webkitTransform = translate;

  return style;
};

Всякий раз, когда список прокручивается, полоса прокрутки также будет меняться, ее движение контролируется этой функцией, посмотрите на прокрутку до и после, ееstyleИзменение:

translateYизменился, то есть имитирует прокрутку панорамированием, а конкретное значение имеет родительский компонент (здесьel-scrollbar) Прошло более.

bar.js/methods

Далее рассмотрим в нем несколько методов, все они связаны с привязкой событий:

// 鼠标按钮在 滚动条上 被按下时的事件处理方法
clickThumbHandler(e) {
  // prevent click event of right button
  // ctrlKey 事件属性可返回一个布尔值,指示当事件发生时,Ctrl 键是否被按下并保持住
  // e.button = 2 表示鼠标右键
  if (e.ctrlKey || e.button === 2) {
    return;
  }
  this.startDrag(e);
  this[this.bar.axis]
    = (e.currentTarget[this.bar.offset] 
    - (e[this.bar.client]
    - e.currentTarget.getBoundingClientRect()[this.bar.direction]));
},

Если нажать при нажатииCtrlИли нажмите правую кнопку мыши, чтобы остановить выполнение события напрямую, и выполните метод перетаскивания при нажатииstartDrag

// 点击并拖拽滚动条
startDrag(e) {
  // 拖动的时候当前元素剩下的监听函数将不会执行
  e.stopImmediatePropagation();
  this.cursorDown = true;

  // 给 document 绑定鼠标移动事件 和 鼠标按钮抬起事件
  on(document, 'mousemove', this.mouseMoveDocumentHandler);
  on(document, 'mouseup', this.mouseUpDocumentHandler);
  // 禁止文字被选中
  // 参考 https://www.jianshu.com/p/701cc19d2c5a
  document.onselectstart = () => false;
}

объяснятьe.stopImmediatePropagation()метод, мы обычно используем меньше. Когда к элементу привязано много событий одного типа, он будет выполнять функции обратного вызова в том порядке, в котором они связаны, но когда мы объявим этот метод в обработчике событий, остальные функции слушателя текущего элемента будут не выполнять.

// 鼠标按钮在 滚动条所在的区域 被按下时的事件处理方法
// 当鼠标点击滚动条 `上方空白处` 时,滚动条向上滚动
// 当鼠标点击滚动条 `下方空白处` 时,滚动条向下滚动
clickTrackHandler(e) {
  // 获取点击的位置距离元素上边距的距离
  // 即 IE 下的 offsetX/offsetY 属性
  const offset
  	= Math.abs(e.target.getBoundingClientRect()[this.bar.direction] 
    - e[this.bar.client]);
  // 滚动条宽/高的一半
  const thumbHalf = (this.$refs.thumb[this.bar.offset] / 2);
  const thumbPositionPercentage
 	  = ((offset - thumbHalf) * 100 / this.$el[this.bar.offset]);

  // 举个例子
  // wrap.scrollTop = -10(假数据) * wrap.scrollHeight / 100
  this.wrap[this.bar.scroll]
    = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
}

Этот метод используется для обработки события, когда щелкают внешний слой полосы прокрутки, и реализует эффект прокрутки, щелкнув пустое место.startDragдляmousemoveа такжеmouseupМетод расчета мониторинга событий подобен вышеописанному, поэтому здесь он не приводится для экономии места.

Посмотрев на привязку событий мыши, следует обратить внимание на то, что должен быть мониторинг отмены событий, особенно событий при движении мыши, что используется в исходном коде.offметод, специфический иonаналогичный.

продолжать решать нашиmain.js/renderяма вырыта внутриscrollbarWidthа такжеtoObject

scrollbar-width.js

Этот файл очень простой, он для расчета ширины полосы прокрутки, которая идет в комплекте с системой, я посмотрел, и этот метод в основном такой же, как и в Интернете.

export default function() {
  if (Vue.prototype.$isServer) return 0;
  // 如果存在 scrollBarWidth 就直接返回
  if (scrollBarWidth !== undefined) return scrollBarWidth;

  const outer = document.createElement('div');
  outer.className = 'el-scrollbar__wrap';
  outer.style.visibility = 'hidden';
  outer.style.width = '100px';
  outer.style.position = 'absolute';
  outer.style.top = '-9999px';
  document.body.appendChild(outer);

  // 没有滚动条时的宽度 = 元素的 offsetWidth
  const widthNoScroll = outer.offsetWidth;
  // 使外层可滚动并且出现滚动条
  outer.style.overflow = 'scroll';

  const inner = document.createElement('div');
  // 设置 width 为 100% 时,强制子元素的内容宽度等于父元素内容宽度
  // 当子元素内容宽度大于父元素的内容宽度时,就会出现滚动条
  inner.style.width = '100%';
  outer.appendChild(inner);

  const widthWithScroll = inner.offsetWidth;
  outer.parentNode.removeChild(outer);
  scrollBarWidth = widthNoScroll - widthWithScroll;

  return scrollBarWidth;
};

Но здесь я думаю, что он использует неправильный атрибут при расчете,innerне следует использоватьoffsetWidthвместо этого вы должны использоватьclientWidth,потому чтоoffsetWidthОн включает в себя полосу прокрутки, поэтому его вообще нельзя вычислить.Я не знаю, то ли они написали это неправильно, то ли я неправильно понял.В любом случае, окончательный результат этого метода равен 0, потому что я запускал его в Google. Chrome для Mac. Еще раз, вы не можете получить ширину полосы прокрутки, установите ееoverflowНет способа сохранить полосу прокрутки, я не знаюWindowsЧто происходит в системе. Пожалуйста, запустите этот метод еще раз и сообщите мне в комментариях, спасибо.

util.js/toObject

function extend(to, _from) {
  // _from 如果是基本数据类型就不会循环
  for (let key in _from) {
    to[key] = _from[key];
  }
  return to;
}

// 把数组里面的所有对象转成一个对象
export function toObject(arr) {
  var res = {};
  for (let i = 0; i < arr.length; i++) {
    if (arr[i]) {
      extend(res, arr[i]);
    }
  }
  return res;
}

Метод здесь очень простой, который заключается в переносе свойств всех объектов в массиве в новый объект путем обхода.Если в массиве есть базовый тип данных, он будет пропущен напрямую.

Здесь нашscrollbarВесь анализ пройден, но еще не время, когда цветы готовы, и есть еще кое-что.el-optionкомпоненты.

el-option

el-optionраздел содержит сам компонент иel-option-groupкомпоненты,optionЭто компонент, который фактически отображает раскрывающийся список, и его отображение на странице<li>этикетка, этоoptionВ структуре шаблона есть слот по умолчанию для отображения текстового содержимого элемента списка. из-заoptionМногоselectМетод компонента связан, поэтому я планирую разобрать его в следующей статье, сначала рассмотрим несколько простых:

// 判断两个参数是否相等
isEqual(a, b) {
  if (!this.isObject) {
    return a === b;
  } else {
    // 拿到 select 组件实例的 valueKey 
    // valueKey 是作为 value 唯一标识的键名,绑定值为对象类型时必填
    const valueKey = this.select.valueKey;
    return getValueByPath(a, valueKey) === getValueByPath(b, valueKey);
  }
}

getValueByPathдаutilИмпортированный внутрь, этот метод в основном используется для доступа к свойствам, указанным объектом:

/**
 * 深层次访问对象的属性
 * @param {Object} object 目标对象
 * @param {string} prop 属性名 xxx.xxx.xxx 形式
 */
export const getValueByPath = function(object, prop) {
  prop = prop || '';
  // paths => [xxx, xxx, xxx]
  // object: {
  //   xxx: {
  //     xxx: {
  //       xxx: 'xxx'
  //     }
  //   }
  // }
  const paths = prop.split('.');
  // 把对象保存起来,以免改变了原有对象
  let current = object;
  let result = null;
  for (let i = 0, j = paths.length; i < j; i++) {
    const path = paths[i];
    if (!current) break;

    // 当到达指定的属性名时,返回它的属性值
    if (i === j - 1) {
      result = current[path];
      break;
    }
    // 否则继续往下遍历
    current = current[path];
  }
  return result;
};

Но на мой взгляд, официальная реализация может быть проще, потому что, поскольку имя атрибута сохраняется в массиве, не лучше ли использовать метод массива, а затем использоватьwhileЭтого можно добиться, зациклив несколько строк кода:

function getValByPath(obj, path) {
  const paths = path.split('.')
  let res = obj
  let prop
  while ((prop = paths.shift())) {
    res = res[prop]
  }
  return res
}

Посмотрите на другой метод:

// 鼠标移动时触发的事件监听方法
hoverItem() {
  // 如果当前项没有被禁用,就设置 select 组件的 `hoverIndex`
  // 它的值为当前列表项在 options 数组里的索引
  if (!this.disabled && !this.groupDisabled) {
    this.select.hoverIndex = this.select.options.indexOf(this);
  }
}

В основном отображается, когда мышь перемещается по элементу спискаhoverсостояние, конкретная реализация помещается вselectв. Остальные здесь нужно будет проанализировать позже, я действительно не двигаюсь... Что касаетсяoption-groupСодержание внутри простое, иoptionРазница не большая, просто взгляните на нее, и я не буду ее здесь писать.

Резюме и размышления

Пока что мы наконецselectАнализируется часть шаблона компонента, обратите внимание, что это часть шаблона, настоящая большая голова не приходит, вselectБольшинство методов в нем занимают около 400 строк, остальные — это какие-то атрибуты и хуки жизненного цикла, о методах я расскажу в следующей статье.selectшаблон:

  • Во-первых, весьselectКомпонент состоит из поля ввода и раскрывающегося списка.
  • Поле ввода используетel-inputкомпонент, раскрывающийся список используетel-select-menuкомпоненты
  • el-select-menuдобавляется к узлу body черезv-showПереключить отображение и скрытие
  • Раскрывающийся список содержит встроенный компонент прокрутки.el-scrollbar
  • Использование пользовательской полосы прокрутки в компоненте прокруткиBarкомпоненты
  • элементы спискаel-optionа такжеel-option-groupоказанныйulэтикетки иliЭтикетка

В этой статье все еще есть нерешенные вопросы:

  • el-tagКомпоненты не анализируются подробно
  • анимацияtransitionКомпонент не анализировался

В заключение я вижуselectОщущение компонентов, у меня ушла неделя на то, чтобы прочитать исходный код и написать эту статью, и я прочитал только небольшую часть.Я должен сказать, что здесь задействовано слишком много знаний.Для такого фронтенда, как я. Сяобай, это действительно слишком сложно. На этой неделе часто бывают моменты, когда я больше не могу его смотреть. Есть некоторые точки знаний, которых я никогда раньше не видел. Постоянно читая документы и блоги, я постепенно вошел в это состояние. Вы берете часть кода и запускаете его в браузере, и вы можете сразу понять принцип, нажав точку останова (я действительно не знаю, как запустить весь проект, и я пробовал много методов без успеха ). В течение этого периода я также просмотрел множество учебных пособий и API-интерфейсов Vue, и я постепенно обновлял свое понимание Vue. Я верю, что стану более опытным в использовании Vue в будущей разработке, потому что понимание концепций реальных проектов , и постепенно, когда вы накопите достаточно, вы сможете сформировать полный замкнутый цикл знаний. При просмотре исходного кода "Задайте еще несколько причин, действительно помогает вам понять это. Другой - идея дизайна компонентов. Это очень абстрактная вещь. Вы не можете понять, просто взглянув на один или два компонента. Вам нужно прочитать много исходного кода компонента и знать, какие проблемы он решает и какие функции, которые у него есть.Почему вы хотите проектировать таким образом?Когда вы смотрите на компонент, вы можете быстро понять эти три вопроса, тогда у вас будет представление о компоненте. Этого можно достичь только в том случае, если вам нужно много читать и практиковаться, вы не можете достичь этого, прочитав несколько блогов и взглянув на два компонента.

Что ж, ждите следующей статьи. Если вам понравилась эта статья, вы можете поставить лайк, чтобы ее увидело больше людей. Если что-то не так с анализом в статье, пожалуйста, укажите на это. Вы также можете добавить мой WeChat [Liu472362746] Обсудите вместе, и я отправлю подробный код наGithubсклад.

портал

【2020.3.15】Elementui Ultra-подробный анализ исходного кода - ввод

[2020.3.16]Сверхдетальный анализ исходного кода ElementUI — Layout

【2020.3.18】Сверхдетальный анализ исходного кода ElementUI - Радио