Серия анализа исходного кода элемента 6-флажок (флажок)

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

Введение

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

Сначала код, код официального сайтакликните сюда

<template>
  <label
    class="el-checkbox"
    :class="[
      border && checkboxSize ? 'el-checkbox--' + checkboxSize : '',
      { 'is-disabled': isDisabled },
      { 'is-bordered': border },
      { 'is-checked': isChecked }
    ]"
    role="checkbox"
    :aria-checked="indeterminate ? 'mixed': isChecked"
    :aria-disabled="isDisabled"
    :id="id"
  >
    <span class="el-checkbox__input"
      :class="{
        'is-disabled': isDisabled,
        'is-checked': isChecked,
        'is-indeterminate': indeterminate,
        'is-focus': focus
      }"
       aria-checked="mixed"
    >
      <span class="el-checkbox__inner"></span>
      <input
        v-if="trueLabel || falseLabel"
        class="el-checkbox__original"
        type="checkbox"
        aria-hidden="true"
        :name="name"
        :disabled="isDisabled"
        :true-value="trueLabel"
        :false-value="falseLabel"
        v-model="model"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false">
      <input
        v-else
        class="el-checkbox__original"
        type="checkbox"
        aria-hidden="true"
        :disabled="isDisabled"
        :value="label"
        :name="name"
        v-model="model"
        @change="handleChange"
        @focus="focus = true"
        @blur="focus = false">
    </span>
    <span class="el-checkbox__label" v-if="$slots.default || label">
      <slot></slot>
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';
  export default {
    name: 'ElCheckbox',
    mixins: [Emitter],
    inject: {
      elForm: {
        default: ''
      },
      elFormItem: {
        default: ''
      }
    },
    componentName: 'ElCheckbox',
    data() {
      return {
        selfModel: false,
        focus: false,
        isLimitExceeded: false
      };
    },
    computed: {
      model: {
        get() {
          return this.isGroup
            ? this.store : this.value !== undefined
              ? this.value : this.selfModel;
        },
        set(val) {
          if (this.isGroup) {
            this.isLimitExceeded = false;
            (this._checkboxGroup.min !== undefined &&
              val.length < this._checkboxGroup.min &&
              (this.isLimitExceeded = true));
            (this._checkboxGroup.max !== undefined &&
              val.length > this._checkboxGroup.max &&
              (this.isLimitExceeded = true));
            this.isLimitExceeded === false &&
            this.dispatch('ElCheckboxGroup', 'input', [val]);
          } else {
            this.$emit('input', val);
            this.selfModel = val;
          }
        }
      },
      isChecked() {
        if ({}.toString.call(this.model) === '[object Boolean]') {
          return this.model;
        } else if (Array.isArray(this.model)) {
          return this.model.indexOf(this.label) > -1;
        } else if (this.model !== null && this.model !== undefined) {
          return this.model === this.trueLabel;
        }
      },
      isGroup() {
        let parent = this.$parent;
        while (parent) {
          if (parent.$options.componentName !== 'ElCheckboxGroup') {
            parent = parent.$parent;
          } else {
            this._checkboxGroup = parent;
            return true;
          }
        }
        return false;
      },
      store() {
        return this._checkboxGroup ? this._checkboxGroup.value : this.value;
      },
      isDisabled() {
        return this.isGroup
          ? this._checkboxGroup.disabled || this.disabled || (this.elForm || {}).disabled
          : this.disabled || (this.elForm || {}).disabled;
      },
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      checkboxSize() {
        const temCheckboxSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
        return this.isGroup
          ? this._checkboxGroup.checkboxGroupSize || temCheckboxSize
          : temCheckboxSize;
      }
    },
    props: {
      value: {},
      label: {},
      indeterminate: Boolean,
      disabled: Boolean,
      checked: Boolean,
      name: String,
      trueLabel: [String, Number],
      falseLabel: [String, Number],
      id: String, /* 当indeterminate为真时,为controls提供相关连的checkbox的id,表明元素间的控制关系*/
      controls: String, /* 当indeterminate为真时,为controls提供相关连的checkbox的id,表明元素间的控制关系*/
      border: Boolean,
      size: String
    },
    methods: {
      addToStore() {
        if (
          Array.isArray(this.model) &&
          this.model.indexOf(this.label) === -1
        ) {
          this.model.push(this.label);
        } else {
          this.model = this.trueLabel || true;
        }
      },
      handleChange(ev) {
        if (this.isLimitExceeded) return;
        let value;
        if (ev.target.checked) {
          value = this.trueLabel === undefined ? true : this.trueLabel;
        } else {
          value = this.falseLabel === undefined ? false : this.falseLabel;
        }
        this.$emit('change', value, ev);
        this.$nextTick(() => {
          if (this.isGroup) {
            this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]);
          }
        });
      }
    },
    created() {
      this.checked && this.addToStore();
    },
    mounted() { // 为indeterminate元素 添加aria-controls 属性
      if (this.indeterminate) {
        this.$el.setAttribute('aria-controls', this.controls);
      }
    },
    watch: {
      value(value) {
        this.dispatch('ElFormItem', 'el.form.change', value);
      }
    }
  };
</script>

Вы выглядите растерянным?Лучше всего открыть официальный сайт и проанализировать принцип, сравнив использование чекбокса по пунктам.

Общая HTML-структура флажка

Аналогично радиокнопке схематическое изображение флажка выглядит следующим образом: он состоит из двух частей: левой и правой, с меткой на внешнем слое и скрывает нативную<input type='checkbox'>

Упрощенная структура html выглядит так

<label ...>
    <span class='el-checkbox__input'>
        <span class='el-checkbox__inner'></span>
        <input type='checkbox' .../>
    </span>
    <span class='el-checkbox__label'>
        <slot></slot>
        <template v-if="!$slots.default">{{label}}</template>
    </span>
</label>

Вот конкретная ссылка на предыдущую статью о переключателях, с акцентом на то, как реализована галочка в синем поле на картинке выше, то есть выбранное состояние.Сначала я подумал, что это что-то похожее на Icon, но это было нет, проверьте Код css выглядит следующим образом

&::after {
      box-sizing: content-box;
      content: "";
      border: 1px solid $--checkbox-checked-icon-color;
      border-left: 0;
      border-top: 0;
      height: 7px;
      left: 4px;
      position: absolute;
      top: 1px;
      transform: rotate(45deg) scaleY(0);
      width: 3px;
      transition: transform .15s ease-in .05s;
      transform-origin: center;
}

Очевидно, этоel-checkbox__innerПсевдоэлемент after класса содержит прямоугольник, только нижняя правая граница которого повернута на 45 градусов, что имеет форму галочки, так что эта галочка является просто чистой реализацией CSS.Преимущество в том, что структура html упрощена, и размахtransitionНажмите, чтобы добавить крючок большую анимацию, вот переход черезtransformизscaleYзначение для реализации, если флажок не установленscaleYОн равен 0, а при его выборе равен 1, и реализуется эффект увеличения галочки.
Следовательно, правильное использование псевдоэлементов упростит написание большого количества ненужного кода.

Принцип флажка в Vue

Во-первых, давайте посмотрим, как реализован флажок в Vue.Понимание этого помогает понять реализацию Element.Официальный сайт описан следующим образом.

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

function genCheckboxModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
) {
  const number = modifiers && modifiers.number
  const valueBinding = getBindingAttr(el, 'value') || 'null'
  const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
  const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
  addProp(el, 'checked',
    `Array.isArray(${value})` +
    `?_i(${value},${valueBinding})>-1` + (
      trueValueBinding === 'true'
        ? `:(${value})`
        : `:_q(${value},${trueValueBinding})`
    )
  )
  addHandler(el, 'change',
    `var $$a=${value},` +
        '?el=$event.target,' +
        `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
    'if(Array.isArray(?a)){' +
      `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
          '?i=_i(?a,?v);' +
      `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '?a.concat([?v])')})}` +
      `else{$$i>-1&&(${genAssignmentCode(value, '?a.slice(0,?i).concat(?a.slice(?i+1))')})}` +
    `}else{${genAssignmentCode(value, '?c')}}`,
    null, true
  )
}

Это код, который обрабатывает v-модель чекбокса.Нам нужно только знать, что делает этот код.Детали не должны быть слишком четкими.Сначала получается код.v:bindЗначение связанного значения, которое является значением в следующем примере кода.Jack(Обратите внимание, что код фактически обрабатывает случай not v:bind, подробности см. в исходном коде), затемgenCheckboxModelв параметрах этой функцииvalueэто значение v-модели, следующееcheckedNames, следующийaddPropЛогика метода: еслиcheckedNamesэто массив, затем запросите по indexOfJackВы вcheckedNames, если это так, добавьте к входным данным проверенный атрибут, чтобы указать, что он выбран, а затем, еслиcheckedNamesЕсли это не массив, то напрямую сравните, равны ли они, чтобы решить, добавлять ли проверенный атрибут к входным данным.

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">

Из вышеприведенного анализа видно, чтоОтмеченное свойство флажка добавляется Vue за кулисами, и нужно ли добавлять свойство, определяется путем сравнения значения
Тогда см.addHandlerметод, этот метод добавляет событие изменения к флажку.После щелчка на собственном флажке его проверенное свойство изменится (true или false), но в VuecheckedNamesЗначение изменится соответственно, вотaddHandlerПроделанная работа, общая логика в этом методе заключается в том, чтобы сначала судитьcheckedNamesЯвляется ли это массивом, если да и флажок установлен, добавьте значение флажкаcheckedNamesВ массиве, если чекбокс не установлен, убрать его из массива (обратите внимание, что здесь не используется splice, а 2 слайса объединяются конкатом в массив, splice изменит исходный массив, так что не будет)

Таким образом, событие onchange также обрабатывается Vue за кулисами, поэтомуcheckedNamesМассив может изменяться синхронно, когда мы нажимаем на разные флажки.

анализ исходного кода флажка

Далее по очереди разбираем функции, перечисленные на официальном сайте.

Отключить функцию

Самый простой способ отключить функцию — использовать следующий код, просто добавьтеdisabledхарактеристики

<el-checkbox v-model="checked1" disabled>备选项1</el-checkbox>

соответствует исходному коду:disabledАтрибуты

 <input
    v-else
    class="el-checkbox__original"
    type="checkbox"
    aria-hidden="true"
    :disabled="isDisabled"
    :value="label"
    :name="name"
    v-model="model"
    @change="handleChange"
    @focus="focus = true"
    @blur="focus = false"
>

,здесьisDisabledЭто вычисляемое свойство, поскольку учитывается наличие компонента группы флажков, компонент группы флажков<el-checkbox-group>Также естьdisabledсвойство, а компонент группы флажков является родительским компонентом компонента флажка

isDisabled() {
        return this.isGroup
          ? this._checkboxGroup.disabled || this.disabled || (this.elForm || {}).disabled
          : this.disabled || (this.elForm || {}).disabled;
      },

Здесь сначала определите, включены ли вы в компонент группы флажков. Если да, атрибут disabled является атрибутом disabled родительского компонента группы флажков. В противном случае это ваш собственный атрибут. Как определить, включен ли он в флажок ? В компоненте группы вставок была представлена ​​предыдущая серия статей.

Компонент группы флажков

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

 <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mike</label>

Но глядя на код Element, он также абстрагируется от отдельного<el-checkbox-group>компонент, преимущество этого заключается в том, что вам не нужно устанавливать v-модель для каждого компонента флажка, просто дайте<el-checkbox-group>Вы можете установить v-модель, и этот абстрактный компонент также может добавлять множество других настраиваемых атрибутов, что эквивалентно добавлению родительского компонента для унифицированного управления некоторыми поведениями всех дочерних флажков.

<el-checkbox-group>Код очень простой, html часть выглядит следующим образом

<template>
  <div class="el-checkbox-group" role="group" aria-label="checkbox-group">
    <slot></slot>
  </div>
</template>

Это div с размещенным в нем слотом, и содержимое слота вставляется пользователем.<el-checkbox>Компонент, реквизиты компонента группы с несколькими вариантами выбора следующие:

 props: {
      value: {},
      disabled: Boolean,
      min: Number,
      max: Number,
      size: String,
      fill: String,
      textColor: String
    },

вvalueЭто использование v-модели на компоненте.Подробности смотрите на официальном сайте и в описании предыдущей статьи.Здесьmin,maxАтрибут управляет максимальным и минимальным количеством флажков, которые можно выбрать в группе флажков.Как это достигается?
Сначала посмотрите на исходный код и обратите внимание, что логика здесь не помещена в<el-checkbox-group>реализованы в<el-checkbox>, потому что на самом деле вы нажимаете<el-checkbox>ввод, поэтому вам нужно реализовать логику в компоненте флажка, соответствующий код выглядит следующим образом

 model: {
        get() {
          return this.isGroup
            ? this.store : this.value !== undefined
              ? this.value : this.selfModel;
        },
        set(val) {
          if (this.isGroup) {
            this.isLimitExceeded = false;
            (this._checkboxGroup.min !== undefined &&
              val.length < this._checkboxGroup.min &&
              (this.isLimitExceeded = true));
            (this._checkboxGroup.max !== undefined &&
              val.length > this._checkboxGroup.max &&
              (this.isLimitExceeded = true));
            this.isLimitExceeded === false &&
            this.dispatch('ElCheckboxGroup', 'input', [val]);
          } else {
            this.$emit('input', val);
            this.selfModel = val;
          }
        }
      },

Эта модель является вычисляемым свойством, на входеv-model="model"Он используется здесь для представления значения v-модели компонента флажка. Чтобы получить и установить использование вычисляемого свойства, обратитесь к официальному веб-сайту. Сначала посмотрите на get и сначала определите, включено ли оно в группу флажков. компонента, если да, то значение модели равноthis.store, Это хранилище также является вычисляемым свойством, как показано ниже.

store() {
        return this._checkboxGroup ? this._checkboxGroup.value : this.value;
      },

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

<el-checkbox-group v-model="checkList">

Следовательно, массив контрольного списка, переданный пользователем, передается дочернему элементу.<el-checkbox>внутри, покаthis._checkboxGroupвisGroupЗначение, присвоенное этому вычисляемому свойству, является его собственным внешним слоем.<el-checkbox-group>компоненты

Давайте еще раз посмотрим на метод set.Set - это метод, который срабатывает при присвоении значения модели.Когда пользователь щелкает флажок, срабатывает событие onchange флажка, и в этом событии присваивается значение, тем самым запуск метода set. Метод set используетisLimitExceededПеременные для оценки того, превышены ли пределы max и min. Если существует атрибут min и длина массива val меньше min, это означает, что предел превышен. В это время установитеisLimitExceededДля true то же самое верно и для max. Значение val обрабатывается в исходном коде Vue, здесь нет необходимости вдаваться в подробности.isLimitExceededКогда оно ложно, то есть когда граница не превышена, используется диспетчеризация для уведомления родительского компонента об обновленном значении val.

Сомнение есть<el-checkbox>привязан к входу внутри@change="handleChange", код показывает, как показано ниже

handleChange(ev) {
        if (this.isLimitExceeded) return;
        let value;
        if (ev.target.checked) {
          value = this.trueLabel === undefined ? true : this.trueLabel;
        } else {
          value = this.falseLabel === undefined ? false : this.falseLabel;
        }
        this.$emit('change', value, ev);
        this.$nextTick(() => {
          if (this.isGroup) {
            this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]);
          }
        });
      }

Приведенный выше handleChange также отправляет значение родительскому компоненту checkGroup, так в чем же разница между этой отправкой и приведенной выше отправкой? После тщательного анализа я обнаружил, что отправка здесь просто уведомление<el-checkbox-group>изменилось собственное значение, в<el-checkbox-group>Вы можете использовать @change для получения измененного значения (пользователь может получить значение для дальнейшей обработки), и предыдущая отправка обновляется.<el-checkbox-group>Значение атрибута v-model, роль этих двух диспетчеров различна, пожалуйста, поймите внимательно
Затем в дескриптореChangethis.$emit('change', value, ev)Указывает, что значение и собственный объект события ev передаются в<el-checkbox>событие onchange, так как пользователю может понадобиться этот интерфейс для получения обновленных данных

Наконец, давайте посмотрим на логику изменения стиля CSS при установленном флажке.

<span class="el-checkbox__input"
      :class="{
        'is-disabled': isDisabled,
        'is-checked': isChecked,
        'is-indeterminate': indeterminate,
        'is-focus': focus
      }"
       aria-checked="mixed"
    >

Этот диапазон представляет смоделированную кнопку-флажок, гдеis-checkedКласс представляет класс стиля при выборе, этот класс определяетсяisCheckedControl, это вычисляемое свойство, код выглядит следующим образом

isChecked() {
        if ({}.toString.call(this.model) === '[object Boolean]') {
          return this.model;
        } else if (Array.isArray(this.model)) {
          return this.model.indexOf(this.label) > -1;
        } else if (this.model !== null && this.model !== undefined) {
          return this.model === this.trueLabel;
        }
      },

суждение первого шагаthis.modelЯвляется ли это логическим типом, обратите внимание на метод суждения здесь,Object.prototype.toString.callЭто самый надежный способ судить. Когда модель логическая, это означает, что это значение управляет самим флажком. Если модель представляет собой массив, оценивается, находится ли метка в массиве или нет. Если да, то это означает что флажок установлен. , тем самымisCheckedЕсли true, метка является атрибутом, определенным пользователем в флажке, представляющим значение флажка, подробности см. на официальном веб-сайте.

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