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

внешний интерфейс исходный код Element Ресурсы изображений

Введение

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

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

Исходная HTML-структура поля ввода

Прежде всего, необходимо понять html-структуру ввода, инкапсулируемого Element.Ниже приведена упрощенная html-структура.

<template>
  <div ...>
    <template v-if="type !== 'textarea'">
      <!-- 前置元素 -->
      <div class="el-input-group__prepend" v-if="$slots.prepend">
        <slot name="prepend"></slot>
      </div>
      
      <!--主体input-->
      <input ...>
      
      <!-- 前置内容 -->
      <span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
       ...
      </span>
      <!-- 后置内容 -->
      <span
        ...
      </span>
      <!-- 后置元素 -->
      <div class="el-input-group__append" v-if="$slots.append">
       ...
      </div>
    </template>
    <textarea v-else>
    </textarea>
  </div>
</template>

Он выглядит большим? На самом деле все очень просто: крайний div используется как обертка для оборачивания элементов внутри, далее идет v-if тега шаблона (шаблон на самом деле рендериться не будет), а нижний — v -else текстовой области.typeЭтот параметр определяет, отображается ли компонент поля ввода.inputещеtextarea, для v-else это textarea, сказать нечего, ключ лежит в предыдущем v-if, внимательно посмотрите на эту структуру, она состоит изпредварительный элемент, основной ввод, предварительный контент, постконтент, постэлементЭти части составлены, так что же они представляют? На картинке ниже ответ

Середина рисунка — это поле ввода, два передних и задних — вспомогательное содержимое, эти два — передний и задний элементы, а значок поиска и даты в поле ввода — переднее и заднее содержимое, поэтому для инкапсуляции такого полный ввод, много кода

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

Средний ввод автоматически сужается, так в чем дело с раскладкой этого брата, у этого брата раскладка похожа наШирина левого столбца неопределенная, а правого столбца адаптивная., Левая колонка неопределенная означает, что ширина растягивается содержимым.Проверьте код css, чтобы знать, что этоtable-cellМакет, мы знаем, что ширина таблиц в таблице является адаптивной.Если столбец очень широкий, другие столбцы станут более узкими, поэтому эту идею можно использовать здесь.Ниже приведен пример макета (ширина левого колонка переменная, правая колонка адаптация), обратите внимание на настройки внешнего контейнераdisplay:table

<div style="display:table" class='wrapper'>
    <div style="display:table-cell" class='left'>
    </div>
    <div style="display:table-cell" class='right'>
    </div>
</div>

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

Поле ввода на самом деле имеет левое и правое отступы, и для большей красоты text-indent здесь не используется для управления положением курсора.

Как можно заметить-webkit-appearance:none,outline:noneТакое использование очень распространено в различных компонентах, и цель состоит в том, чтобы удалить стили, отображаемые самим браузером, и указать стили единообразно. здесьtransitionНа самом деле для перехода используется кривая Безье. Другими словами, время перехода составляет всего 0,2 секунды. Можно ли увидеть это с помощью кривой Безье? непосредственныйeaseЭто тоже должно быть возможно!

Реализация отключенного состояния

Отключение простое, передается пользователемdisabledсвойства для управления, следующий код

<el-input
  placeholder="请输入内容"
  v-model="input1"
  :disabled="true">
</el-input>

через исходный код<input :disabled="inputDisabled" ...>Для управления функцией ввода отключено, этоinputDisabledвычисляемое свойство

inputDisabled() {
        return this.disabled || (this.elForm || {}).disabled;
      },

Это потому, что необходимо судить, что если ввод включен в форму, если форма отключена, она будет отключена естественным образом. Отключение стиля поля ввода контролируется классом самого внешнего div

<div :class=[{'is-disabled': inputDisabled}...]>...</div>

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

&::placeholder {
        color: $--input-disabled-placeholder-color;
      }

атрибуты элемента ввода

Посмотрев свойства нативного ввода в компоненте, я узнал много знаний

<input
        :tabindex="tabindex"
        v-if="type !== 'textarea'"
        class="el-input__inner"
        v-bind="$attrs"
        :type="type"
        :disabled="inputDisabled"
        :readonly="readonly"
        :autocomplete="autoComplete"
        :value="currentValue"
        ref="input"
        @compositionstart="handleComposition"
        @compositionupdate="handleComposition"
        @compositionend="handleComposition"
        @input="handleInput"
        @focus="handleFocus"
        @blur="handleBlur"
        @change="handleChange"
        :aria-label="label"
      >

Вау, здесь так много свойств и методов~~ Это то, что должен реализовать зрелый компонент, посмотрите сначалаtabindex, заключается в управлении порядком доступа после нажатия клавиши табуляции. Если tabindex передается пользователем, если для него задано отрицательное число, к нему нельзя получить доступ с помощью клавиши табуляции. Если для него установлено значение 0, он будет доступен в конце. потомv-if="type !== 'textarea'"Контролирует отрисовку этого ввода или нет, пользователь передает атрибут type для управления, а затем класс вводаel-input__inner, описанный ранее, а затемv-bind="$attrs"Это предложение, к чему это предложение? Откройте официальный сайт, чтобы узнать

Это немного сложно читать, но вот пример

<el-input maxlength="5" minlength="2">
</el-input>

Здесь мы даем<el-input>Добавлен компонент 2Роднойсвойства, обратите внимание, что этих двух собственных свойств нет в опоре.Эти два свойства используются для управления максимальным вводом и минимальной длиной ввода ввода, поэтому эти два свойства теперь размещаются только в родительском элементе<el-input>о том, как передать его премьеру<el-input>Как насчет собственного дочернего элемента ввода внутри? Если не пройти, эти 2 атрибута не будут работать, потому что эти 2 атрибута недоступны для дочернего ввода. Ответ черезv-bind="$attrs"для достижения, он будет родительским элементом всехне реквизитФункции привязаны к входу дочернего элемента, иначе вам придется объявлять maxlength, minlength в свойствах, и количество кода увеличится. Это$attrsпреимущество
смотреть вниз:readonly="readonly" :autocomplete="autoComplete", Эти два атрибута являются собственными атрибутами, переданными пользователем для управления доступом только для чтения и автозаполнением поля ввода, а затем значением поля ввода.:value="currentValue"CurrentValue здесь находится в данных

currentValue: this.value === undefined || this.value === null
          ? ''
          : this.value,

Если пользователь не<el-input>Напишите v-model (официальный сайт ссылки на принцип v-model), затем значение не передается, поэтому currentValue является пустой строкой, в противном случае это переданное значение, затемref="input"В одном предложении ref используется для регистрации справочной информации для элемента или подкомпонента. Справочная информация будет зарегистрирована в объекте $refs родительского компонента, что должно облегчить последующему коду непосредственное получение dom собственного ввода.

Тогда эти 3 предложения

@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"

Это нельзя недооценивать.Эти три метода являются родными методами.Вот краткое введение.Официальные определения следующиеСобытие «compositionstart» запускается перед вводом фрагмента текста (аналогично событию «keydown», но это событие происходит только перед вводом ряда видимых символов, для чего может потребоваться ряд операций с клавиатурой, распознавание речи или метод ввода по щелчку). альтернативы)Проще говоря, при переключении китайского метода ввода при вводе пиньинь (реальное содержание не было введено во ввод в это время) сначала будет запускаться композиция, затем каждый раз при вводе буквы пиньинь будет запускаться композицияобновление, и наконец, введенный китайский язык будет заполнен составом.Когда срабатывает композицияstart, текстовое поле будет заполнено «фиктивным текстом» (текст, который необходимо подтвердить), и в то же время будет срабатывать событие ввода; когда срабатывает композицияконец, оно будет заполнено фактическим содержимым (подтвержденный текст). ), поэтому, если вы не хотите запускать событие ввода, вам нужно установить логическую переменную для управления

На картинке выше фактический текст будет заполнен после нажатия пробела. Ввод английского языка или цифр не вызовет эти три события.

Итак, вопрос в том, почему Element должен устанавливать обработчики для этих трех событий? Причина очень проста, мы определенно не хотим напрямую вызывать изменение события ввода в процессе ввода пиньинь.<el-input v-model="inputValue"></el-input>Ожидается, что значение inputValue в inputValue будет изменено после завершения ввода, поэтому требуется специальная обработка.handleCompositionИсходный код , обратите внимание, что здесь написан только один метод вместо 3. Тип события оценивается по event.type для упрощения кода, вы можете учиться на нем.

handleComposition(event) {
        if (event.type === 'compositionend') {
          this.isOnComposition = false;
          this.currentValue = this.valueBeforeComposition;
          this.valueBeforeComposition = null;
          this.handleInput(event);
        } else {
          const text = event.target.value;
          const lastCharacter = text[text.length - 1] || '';
          this.isOnComposition = !isKorean(lastCharacter);
          if (this.isOnComposition && event.type === 'compositionstart') {
            this.valueBeforeComposition = text;
          }
    }
},

Здесь сначала в данных определяется логическая переменная.isOnComposition, Эта переменная используется для определения того, воспроизводится ли пиньинь. Начальное значение равно false. Когда пиньинь запустится, он будет запущен.compositionstartсобытие, обновлениеisOnComposition,пройти черезthis.isOnComposition = !isKorean(lastCharacter)Чтобы обновить, логика здесь заключается в том, чтобы судить, является ли последний символ ввода корейским, а корейский судят по регулярным выражениям.Что касается того, почему судить о последнем символе корейского языка, непонятно~ Если он китайский, тоisOnCompositionЕсли это правда, то здесь труднее понять последнее, если при игре на пиньине иcompositionstartсобытие, используйтеvalueBeforeCompositionПеременная сохраняет текущий текст, то есть сохраняет текстовое содержимое во вводе до этого набора, этогоvalueBeforeCompositionФункция будет представлена ​​позже, см. далееif (event.type === 'compositionend')Содержимое в , когда пиньинь закончен, срабатываетcompositionend, затем установитеisOnCompositionНеверно указывать, что ввод завершен, а затем обратите внимание, что здесь будет запущен ручной триггер.this.handleInput(event)(handleInput — это v-on:input, привязанный к входу), потому что, когда последний ввод завершен,compositionendБудет вinputСрабатывает после события, в это времяisOnCompositionТем не менее, эмиссия в handleInput ниже не может быть запущена для передачи значения нового ввода родительскому компоненту, поэтому здесь вам нужно вручную вызвать handleInput один раз, пожалуйста, внимательно поймите здесь!

handleInput(event) {
        const value = event.target.value;
        this.setCurrentValue(value);
        if (this.isOnComposition) return;
        this.$emit('input', value);
      },

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

Пустая реализация

<el-input>в случае добавленияclearableДля атрибутов после ввода текста появится значок крестика, после нажатия которого введенное содержимое будет очищено, как показано на рисунке ниже.

Сначала посмотрите на структуру html, ниже приведен html-код содержимого сообщения.

<!-- 后置内容 -->
      <span
        class="el-input__suffix"
        v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon">
        <span class="el-input__suffix-inner">
          <template v-if="!showClear">
            <slot name="suffix"></slot>
            <i class="el-input__icon"
              v-if="suffixIcon"
              :class="suffixIcon">
            </i>
          </template>
          
          
         <i v-else
            class="el-input__icon el-icon-circle-close el-input__clear"
            @click="clear"
          ></i>
          
          
        </span>
        <i class="el-input__icon"
          v-if="validateState"
          :class="['el-input__validateIcon', validateIcon]">
        </i>
      </span>

средняя часть<i>Это очистить кнопку, это тег i, есть событие щелчка, предыдущий проходshowClearЧтобы определить, нужно ли отображать кнопку очистки, логика следующая.

showClear() {
        return this.clearable &&
          !this.disabled &&
          !this.readonly &&
          this.currentValue !== '' &&
          (this.focused || this.hovering);
      }

Первый шаг этого вычисляемого свойства зависит от того, добавил ли пользователь свойство для отображения кнопки очистки. Если нет, оно не будет отображаться. Если есть, продолжайте судить. Это только в неотключенном и не- состояние только для чтения и значение текущего ввода не пусто и ввод Отображается только при получении фокуса или перемещении мыши вверх.Условий немного больше.
Затем посмотрите на очистку, чтобы очистить этот метод

clear() {
        this.$emit('input', '');
        this.$emit('change', '');
        this.$emit('clear');
        this.setCurrentValue('');
        this.focus();
      }

Предложений на самом деле 5, но меньше быть не может.Первый эмит уведомляет родительский компонент о том, что его собственное значение стало пустым, тем самым обновляя<el-input v-model="v">Данные в v пусты, а второе предложение emit запускает событие изменения родительского компонента, так что в<el-input v-model="v" @change="inputChange">Событие можно отслеживать в inputChange в .Третий эммит запускает метод @clear родительского компонента, чтобы сообщить родительскому компоненту, что он был опустошен.Четвертое предложение обновляет его currentValue до пустого, а пятая игра позволяет вход get Focus для удобного ввода

Высокоадаптивная реализация Textarea

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

Однако обработка Element может сделать его адаптивным по высоте, то есть полоса прокрутки не появится.

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

function calcTextareaHeight(){
  ...  
  let height = hiddenTextarea.scrollHeight;
  const result = {};
  ...
  result.height = `${ height }px`;
  return result
}

Здесь высота равна scrollHeight, то есть высоте полосы прокрутки. Здесь высота увеличивается, а затем высота возвращается и привязывается к стилю ввода для динамического изменения высоты текстовой области. Конкретный код очень сложно и требует обработки.Максимальная и минимальная высота и т.д., см. github