Серия анализа исходного кода элемента 7-InputNumber (поле цифрового ввода)

внешний интерфейс исходный код Element Vue.js

Введение

Поле ввода числа, как показано на рисунке ниже, представляет собой просто поле ввода с кнопками «плюс» и «минус». Оно в основном используется для добавления и уменьшения количества товаров в корзине. Этот компонент поля ввода не должен вызывать затруднений на первый взгляд. , но у конкретной реализации Element есть чему поучиться. , Читать исходный код действительно сложно! Код официального сайтакликните сюда

HTML-структура поля ввода числа

HTML-структура этого компонента относительно проста, на первый взгляд я бы подумал, что это div на внешнем слое, input на внутреннем слое и span слева и справа в виде кнопок.Посмотрев исходный код , это действительно так.Упрощенная структура html выглядит следующим образом

<div class='el-input-number'>
    <span class="el-input-number__decrease"></span>
    <span class="el-input-number__increase"></span>
    <el-input></el-input>
</div>

Первые 2 пролета - кнопки плюс и минус, последний<el-input>Это компонент поля ввода, инкапсулированный ранее. Обратите внимание, что это не собственный ввод. Здесь стоит упомянуть, что два интервала абсолютно позиционированы, и<el-input>Левый и правый отступы равны 50px, как показано ниже.

Другими словами, две кнопки «плюс» и «минус» здесь размещены в позиции заполнения ввода, что является отношением сдерживания, а не отношением бок о бок.Два интервала абсолютно позиционированы, слева: 1 слева , справа: справа 1. Этот метод реализации Преимущества, на мой взгляд, такие, как показано ниже

Когда поле ввода получает фокус, граница поля ввода будет выделена, давая людям ощущение, что три части составляют единое целое.Обработка CSS очень проста.Если три части расположены рядом, левая и правая две пролеты нужно обрабатывать отдельно.

Анализ отдельных частей

Первый взгляд на внешний div

<div
    @dragstart.prevent
    :class="[
      'el-input-number',
      inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
      { 'is-disabled': inputNumberDisabled },
      { 'is-without-controls': !controls },
      { 'is-controls-right': controlsAtRight }
    ]">

Первый ряд@dragstart.preventКогда я впервые увидел это, я был ошеломлен! Это предложение указывает на то, что поведение перетаскивания div по умолчанию запрещено.Здесь не очень понятно.Прежде всего, если div нужно перетаскивать, он должен быть установленdraggable="ture"Это работает, а почему перетаскивание должно быть запрещено? Я пытался удалить это предложение, а затем перетащить компонент

Обнаружено, что когда вы выбираете число на входе, вы можете перетащить число.Число светлого цвета под картинкой выше - это то, как оно выглядит, когда вы перетаскиваете его.Также есть шаблон, запрещенный мышью, который не может быть захвачен.draggable="ture"Не удается перетащить выбранные числа
Затем классовая часть div'el-input-number'Базовый класс внешнего div указывается следующим образом

Видимый div устанавливается как встроенный элемент встроенного блока, а затем устанавливается ширина.Поскольку ширина компонента не изменится с изменением содержимого, ширина фиксирована, а следующие три класса управляют тем, будет ли компонент отключена (отключить логику. Предыдущие статьи были проанализированы), отображать ли кнопки плюс и минус, а также размещать ли кнопки с правой стороны, см. рисунок ниже

Добавляются ли эти три класса, понимается пользователем, передающим соответствующие реквизиты.Код scss на изображении выше, над которым я работал в течение длительного времени, — это кнопка минус в правом нижнем углу изображения выше.

@include when(controls-right) {
@include e(decrease) {
      right: 1px;
      bottom: 1px;
      top: auto;
      left: auto;
      border-right: none;
      border-left: $--border-base;
      border-radius: 0 0 $--border-radius-base 0;
    }
}

Это значит, когдаcontrols-rightПосле добавления классаdecreasecss этого класса изменен на вышеуказанное содержание, то есть кнопка минус ставится из исходного левого в правый нижний угол, я сначала не понимаю.top:auto,left:autoЧто это такое?Позже консольная отладка узнала, что, поскольку исходная вершина класса декады равна 1px, а левая — 1px, когдаcontrols-rightПосле того, как класс добавлен, для top и left необходимо установить значение auto, чтобы браузер мог автоматически вычислять top и left, иначе исходные top: 1px, left: 1px не могут быть перезаписаны. Еще стоит отметить, что высота кнопок «плюс» и «минус» здесь задается следующим образом.

height: auto;
line-height: #{($--input-height - 2) / 2};

Укажите высоту как auto и растяните высоту, установив значение высоты строки на половину высоты поля ввода минус ширина границы.Если вы прямо установите высоту на половину высоты, все должно быть в порядке, верно? ? Затем текст в поле ввода центрируетсяtext-align:centerвыполнить

Далее рассмотрим логическую реализацию кнопок сложения и вычитания клавиш.Хтмл-код выглядит следующим образом.Эта кнопка реализована спаном,а не нативной кнопкой.

<span
      class="el-input-number__decrease"
      role="button"
      v-if="controls"
      v-repeat-click="decrease"
      :class="{'is-disabled': minDisabled}"
      @keydown.enter="decrease">
      <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i>
</span>

Роль атрибута роли состоит в том, чтобы сообщить приложениям специальных возможностей (таким как программы для чтения с экрана, удобные программы для слепых людей для доступа в Интернет), что роль, которую играет этот элемент, в основном предназначена для людей с ограниченными возможностями. Использование роли может улучшить читабельность и семантику текста, а затем элементы управления v-if являются логическим значением, которое является реквизитом, передаваемым пользователем для управления отображением кнопки, а затем:classОпределяет, отображает ли кнопка отключенный стиль,@keydown.enterЭто снова меня смутило, это прослушивание нажатия клавиши ввода. Соответствующие инструкции на официальном сайте Vue заключаются в том, чтобы добавить это событие к вводу. Когда ввод получает фокус, нажатие ввода вызовет соответствующее событие, но почему это должно быть добавлено в диапазон?@keydown.enter, пробовал нажимать энтер и ничего не происходит, короче я тут не разбираюсь

Потом я обнаружил, что без этой кнопки нет события @click, а вся логика обработки кликов вынесенаv-repeat-click="decrease"Внутри, помимо клика по операции увеличения или уменьшения числа, есть еще мышь, которая продолжает нажимать и удерживать, чтобы быстро увеличивать или уменьшать число.Вся логика реализована через пользовательские директивы в Vue.Обычно используются пользовательские директивы. Чтобы работать с базовым элементом dom, активируйте определенную логику. существуетdirectivesобъявить в атрибуте

directives: {
      repeatClick: RepeatClick
}

Этот ключ (repeatClick) соответствуетv-repeat-click,value(RepeatClick) — это импортированный метод, см. код ниже

import { once, on } from 'element-ui/src/utils/dom';
export default {
  bind(el, binding, vnode) {
    let interval = null;
    let startTime;
    const handler = () => vnode.context[binding.expression].apply();
    const clear = () => {
      if (new Date() - startTime < 100) {
        handler();
      }
      clearInterval(interval);
      interval = null;
    };

    on(el, 'mousedown', (e) => {
      if (e.button !== 0) return;
      startTime = new Date();
      once(document, 'mouseup', clear);
      clearInterval(interval);
      interval = setInterval(handler, 100);
    });
  }
};

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

используется здесьbindФункцию ловушки можно понимать как однократный вызов инициализации.Вы думаете, что эта инструкция должна привязывать событие щелчка к элементу, поэтому вам нужно вызвать ее только один раз в привязке, а затемbindТри параметраel,binding,vnodeПредставляет действующий дом, объект привязки, который предоставляет различную информацию, и виртуальный узел, сгенерированный компиляцией Vue. Объект привязки выглядит следующим образом

существуетbindЛогика этой функции-ловушки должна запускать метод сложения и вычитания чисел в поле ввода Этот метод написан в компоненте.methodsВнутри, то как получить этот метод, следующее предложение может получить

const handler = () => vnode.context[binding.expression].apply();

Я могу только сказать, что это предложение слишком высококлассное. Вы должны прочитать исходный код, чтобы написать его. Прежде всего, vnode — это виртуальный узел, сгенерированный vue, который является просто объектом js. В нем много атрибутов. это, тоcontextЧто это такое, посмотрите исходный код vue, чтобы знать, что структура vnode следующая

контекст - этоComponentТип структуры данных, этот Компонент — это структура, определяемая потоком, вы можете увидеть содержимое в потоке в исходном коде vue, Компонент — это компонент, поэтому этот контекст — это контекст компонента, в котором находится vnode, а затем посмотри наbinding.expression, официальный сайт говорит, что этоv-repeat-click="decrease"Метод уменьшения в , этот метод прописан в методах компонента, затемcontext[binding.expression]то естьcontext['decrease']Итак, я получил метод удаления в компоненте, который аналогичен его использованию в компоненте.this.decreaseто же самое, то последнееapply()Это очень странно. Использование apply заключается в том, что первый параметр параметра указывает целевой объект для выполнения. Если он равен нулю или не определен, это означает, что метод вызывается в окне. Здесь нет параметра, то есть undefined, поэтому оно выполняется в окне. Я не уверен, правильно это или нет, поэтому я изменил это предложение на

const handler = () => vnode.context[binding.expression].apply(vnode);

Ошибки нет, и я не знаю, почему можно применить() напрямую.Я изменю вышесказанное на следующее, то есть выполнить функцию напрямую, об ошибке не сообщается, все в норме

const handler = () => vnode.context[binding.expression]()

назадbindЛогика метода обнаружила, что нет никакогоclickПохоже, что события щелчка мыши не связаны, нажмите здесь, потому что вы хотите иметь дело с методом триггера непрерывного уменьшения, поэтому непрерывное нажатие и щелчок смешивают оба вместе, как показано ниже.

on(el, 'mousedown', (e) => {
      if (e.button !== 0) return;
      startTime = new Date();
      once(document, 'mouseup', clear);
      clearInterval(interval);
      interval = setInterval(handler, 100);
});

onЭтот метод находится вне каталога исходного кода.Поскольку можно использовать и другие компоненты, он извлекается в общедоступный метод и помещается в каталог util. Первый взглядonкод

export const on = (function() {
  if (!isServer && document.addEventListener) {
    return function(element, event, handler) {
      if (element && event && handler) {
        element.addEventListener(event, handler, false);
      }
    };
  } else {
    return function(element, event, handler) {
      if (element && event && handler) {
        element.attachEvent('on' + event, handler);
      }
    };
  }
})();

Этот метод предназначен для привязки событий к элементам, а if-else обрабатывает ситуацию совместимости,attachEventэто метод, т.е.addEventListenerэто метод других основных браузеров.onТретий параметр — это функция обработчика событий,onв первом предложенииif (e.button !== 0) returnизe.buttonкакая кнопка мыши была нажата

Если он не равен 0, это означает, что левая кнопка не нажата, так как обычно обрабатывается только событие щелчка левой кнопки.onclickреагирует только на нажатие левой кнопки мыши, аonmousedownПотом реагирует на нажатие 3-х клавиш, так что тут надо различать.

onПоследнее предложениеinterval = setInterval(handler, 100)Таймер настроен на регулярное выполнение метода обработчика, чтобы вызвать событие увеличения или уменьшения числа каждые 0,1 с.Затем мы думаем, что при нажатии мыши событие добавляется к элементу dom: обработчик выполняется регулярно, тогда он должен будет уничтожен, когда мышь поднята.Таймер, в противном случае метод обработчика будет запускаться бесконечно, заставляя число все время увеличиваться или уменьшаться, поэтомуonce(document, 'mouseup', clear)Это предложение, чтобы уничтожить таймер, когда мышь поднята, сначала посмотрите на метод очистки

const clear = () => {
      if (new Date() - startTime < 100) {
        handler();
      }
      clearInterval(interval);
      interval = null;
    };

Внутри есть clearInterval, чтобы уничтожить таймер. Предыдущая логика if очень важна. Запишите время, когда мышь нажата, и определите текущее время, когда мышь поднята - время нажатия once(document, 'mouseup', clear),onceявляется функцией высшего порядка, которая запускается только один раз, код выглядит следующим образом

export const once = function(el, event, fn) {
  var listener = function() {
    if (fn) {
      fn.apply(this, arguments);
    }
    off(el, event, listener);
  };
  on(el, event, listener);
};

Это способ записи один раз в режиме наблюдателя, который, по сути, мультиплексирует событие on, но изменяется третий параметр on, fn будет выполняться один раз в прослушивателе, а затем метод off будет использоваться для удаления прослушивателя. , тем самым достигая цели выполнения только один раз.Еще один момент, который следует отметить, это то, что первым параметром метода Once является документ, который также очень важен.Вы можете подумать, что если вы привязываете onmousedown к кнопкам «плюс» и «минус», вы должны привязать onmouseup к кнопкам «плюс» и «минус». вызовет ошибки. Рассмотрим ситуацию, когда вы нажимаете мышью на кнопки «плюс» и «минус», затем перемещаете мышь за пределы кнопки, а затем отпускаете мышь, вы обнаружите, что в это время число все еще увеличивается, это ошибка, поэтому она должна быть в самой внешней части документа. Привяжите mouseup к элементу dom слоя, чтобы всегда можно было отреагировать на событие mouseup, иначе перемещение мыши приведет к постоянному увеличению числа.

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