Введение
Радиокнопка кажется простым компонентом, но на самом деле она имеет много знаний и сложнее.Если написать нативную радиокнопку в html, то это действительно просто, но не так просто инкапсулировать полноценный радиокомпонент Далее, давайте сначала представим некоторые принципы радиокнопок Vue, а затем проанализируем реализацию радиокнопок Element.
Родное радио против Vue Radio
Родная радиокнопка очень проста.Если мы хотим реализовать мужскую и женскую группу радиокнопок, коду нужны только следующие предложения
<input type="radio" name="sex" value="male" checked>男</input>
<input type="radio" name="sex" value="female">女</input>
Мужской переключатель выше добавляетcheckedатрибут, указывающий, что он выбран,valueАтрибут представляет значение переключателя и может быть добавлен к каждому входу.onchangeа такжеonclickсобытие, чтобы получить его значение, нажав, или вы можете пройти все кнопки ввода радио после нажатия кнопки, получитьcheckedсобственностьtrue, а затем получить егоvalue
Обратите внимание на то, как сделать группу опций радио взаимоисключающими, то есть одновременно можно выбрать только одну опцию радио.nameатрибут - это то, что это делает, помещая некоторые переключателиnameУстановите одно и то же значение для достижения эффекта взаимного исключения
Радиокнопка Vue отличается, код выглядит следующим образом
v-modelдобиться взаимного исключения,v-modelЗначение данных - это данные в данных, и выполняется двухсторонняя привязка, поэтому видно, что она не прошлаnameсвойства для достижения взаимного исключения, то как этого добиться? Во-первых, давайте разберемся в сути v-model, v-model по сути является синтаксическим сахаром.
function genRadioModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
const number = modifiers && modifiers.number
let valueBinding = getBindingAttr(el, 'value') || 'null'
valueBinding = number ? `_n(${valueBinding})` : valueBinding
addProp(el, 'checked', `_q(${value},${valueBinding})`)
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
}
Приведенный выше код является кодом для обработки модели радиокнопки,genRadioModelв параметреvalueявляется значением входного значения, иvalueBindingЗначение представляет собой значение v-bind:value в v-model.
<input type="radio" id="jack" value="Jack" v-model="name">
Если пример как выше, тоaddPropЭтот метод будетcheckedстоимость имущества_q('Jack',name)помещается в список атрибутов, где _qlooseEqualАббревиатура метода, указывающая на свободное сравнение (если это объект, то оно преобразуется в сравнение строк через JSON.stringify, иначе напрямую преобразуется в String() и сравнивается), совпадают ли два значения, поэтому логика здесь ясна, если переключатель Значение значения совпадает со значением v-модели, то добавьтеcheckedАтрибут, указывающий, что радиовыбор выбран, естественно значение других радиокнопок отличается от значения v-model, поэтому он не в выбранном состоянии, и нет проверенного атрибута, поэтому достигается эффект взаимного исключения .
Анализ исходного кода
Исходники всей радиодетали не слишком длинные, но познавательных пунктов в ней много, сначала заходим в исходники, код официального сайтакликните сюда
<template>
<label
class="el-radio"
:class="[
border && radioSize ? 'el-radio--' + radioSize : '',
{ 'is-disabled': isDisabled },
{ 'is-focus': focus },
{ 'is-bordered': border },
{ 'is-checked': model === label }
]"
role="radio"
:aria-checked="model === label"
:aria-disabled="isDisabled"
:tabindex="tabIndex"
@keydown.space.stop.prevent="model = isDisabled ? model : label"
>
<span class="el-radio__input"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<span class="el-radio__inner"></span>
<input
class="el-radio__original"
:value="label"
type="radio"
aria-hidden="true"
v-model="model"
@focus="focus = true"
@blur="focus = false"
@change="handleChange"
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
</span>
<span class="el-radio__label" @keydown.stop>
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>
import Emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElRadio',
mixins: [Emitter],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
componentName: 'ElRadio',
props: {
value: {},
label: {},
disabled: Boolean,
name: String,
border: Boolean,
size: String
},
data() {
return {
focus: false
};
},
computed: {
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
model: {
get() {
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
if (this.isGroup) {
this.dispatch('ElRadioGroup', 'input', [val]);
} else {
this.$emit('input', val);
}
}
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
radioSize() {
const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
return this.isGroup
? this._radioGroup.radioGroupSize || temRadioSize
: temRadioSize;
},
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
tabIndex() {
return !this.isDisabled ? (this.isGroup ? (this.model === this.label ? 0 : -1) : 0) : -1;
}
},
methods: {
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model);
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
};
</script>
Сначала проанализируйте часть шаблона, чтобы проанализировать компонент, вы должны сначала выяснить html-структуру компонента.Приведенная выше структура кода упрощается следующим образом.
<label ...>
<span class='el-radio__input'>
<span class='el-radio__inner'></span>
<input type='radio' .../>
</span>
<span class='el-radio__label'>
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
Видно, что весь компонент представляет собой внешнюю метку со спанами 2. Мы знаем, что родная метка радио уродлива, а стиль неодинаков в каждом браузере, поэтому мы должны сами реализовать стиль всех радиокнопок. общий подход заключается в том, чтобы скрыть реальный ввод. Я использую div или span для имитации метки ввода. Функция метки на самом внешнем слое заключается в расширении области действия щелчка мыши. Независимо от того, нажат ли он на текст или на ввод , ответ может быть запущен.Конечно, атрибут id ввода связан с атрибутом for следующим образом, также может быть достигнуто
<input id='t' type='radio'>
<label for='t'>点此</label>
Первая называется неявной ссылкой, а вторая — отображаемой ссылкой. Очевидно, что первая не нуждается в идентификаторе. Первая определенно хороша. Два встроенных спана в метке расположены горизонтально, как показано на следующем рисунке.
display:noneилиvisibility:hidden, Если он отсутствует или скрыт, щелчок мыши не может быть вызван,Толькоopacity:0Для достижения цели это место, которое требует внимания
Далее смотрим на второй спан в метке, этот спан и есть текст, который мы заполнили
<span class='el-radio__label'>
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
Этот диапазон обрабатывается, и слот отображается по умолчанию.<el-radio>а также</el-radio>Текст между, обратите внимание на шаблон, если ничего не заполняем, например, пишем
<el-radio label='1'></el-radio>
Окончательный текст отображается как значение его метки
$slot.defaultЧтобы определить, есть ли подэлементы для принятия решения об отображении, обратите внимание, что сам шаблон не будет отображаться, а будет действовать только как заполнитель.
этикетка анализ этикетки
Тег label имеет множество атрибутов, рассмотрим их по очереди
<label
class="el-radio"
:class="[
border && radioSize ? 'el-radio--' + radioSize : '',
{ 'is-disabled': isDisabled },
{ 'is-focus': focus },
{ 'is-bordered': border },
{ 'is-checked': model === label }
]"
role="radio"
:aria-checked="model === label"
:aria-disabled="isDisabled"
:tabindex="tabIndex"
@keydown.space.stop.prevent="model = isDisabled ? model : label"
>
Первое первое предложениеclass="el-radio"Указывает базовый класс этикетки, что в нем?
@include b(radio) {
color: $--radio-color;
font-weight: $--radio-font-weight;
line-height: 1;
position: relative;
cursor: pointer;
display: inline-block;
white-space: nowrap;
outline: none;
font-size: $--font-size-base;
Это не что иное, как указание некоторых очень простых стилей CSS, стилей мыши, без разрывов строк, без контуров, размера и цвета шрифта и т. д.
затем второе предложение:classУказывает класс динамической привязки, отключен ли он, находится ли он в фокусе, есть ли у него граница, выделен ли он и т. д. Сначала посмотрите, отключен ли классis-disabled, часть кода scss выглядит следующим образом
.el-radio__inner {
background-color: $--radio-disabled-input-fill;
border-color: $--radio-disabled-input-border-color;
cursor: not-allowed;
&::after {
cursor: not-allowed;
background-color: $--radio-disabled-icon-color;
}
Видно, что отключенный класс должен изменить цвет фона и цвет границы, а также стиль мыши в запрещенный символ.Конечно, это только запрет стиля.Как реализован функциональный запрет? Функциональное отключение достигается установкой отключенного атрибута ввода, реального ввода в следующем исходном коде.:disabled="isDisabled"В одном предложении радиокнопку запрещается нажимать
<input
class="el-radio__original"
:value="label"
type="radio"
aria-hidden="true"
v-model="model"
@focus="focus = true"
@blur="focus = false"
@change="handleChange"
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
isDisabledявляется вычисляемым свойством, код выглядит следующим образом
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
Здесь сначала черезisGroupЧтобы определить, находитесь ли вы в группе переключателей, группа переключателей также является компонентом Element, код выглядит следующим образом, и операция выполняется путем объединения ряда переключателей, чтобы сформировать группу блоков, здесь вам нужно только установить V-образная модель в большинстве случаев Внешний слой может быть
<el-radio-group v-model="radio2">
<el-radio :label="3">备选项</el-radio>
<el-radio :label="6">备选项</el-radio>
<el-radio :label="9">备选项</el-radio>
</el-radio-group>
ТакisGroupЧто это такое, посмотрите на код, это вычисляемое свойство, сначала получите родительский компонент текущего компонента, а затем проверьте, является ли его имя компонентаElRadioGroupТо есть группа радиокнопок, если нет, продолжайте проверять родителя родителя.Знания здесь были введены в предыдущей статье. Этот метод найдет самого близкого к себе родителяElRadioGroupкомпоненты
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
Оглядываясь назад на отключающую логику, когда сам включен вгруппа переключателейКогда внутри компонента, отключен он или нет, равно отключена ли группа переключателей или нет. Это нормально. Ведь вся группа блоков отключена, и она отключена. Если это только один переключатель компонент кнопки, отключение мое собственноеdisabledэтоprop
отключить логический конец, затем{ 'is-focus': focus }, это предложение показывает, получена ли метка labelis-focusКласс, управляемый фокусом, а фокус вводится выше@foucusа также@blurОбработка, то есть, имеет ли вход фокус, следующийis-borderedЕсть ли радиопередача границей, контролируется пограничным имуществом, переданным пользователем, и следующееis-checkedКласс представляет стиль выбранной в данный момент радиокнопки черезmodel===labelДля управления моделью является вычисляемое свойство
model: {
get() {
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
if (this.isGroup) {
this.dispatch('ElRadioGroup', 'input', [val]);
} else {
this.$emit('input', val);
}
}
},
Геттер и сеттер определены выше.Геттер сначала определяет, находится ли он в компоненте группы переключателей, если это старое значение группы переключателей, в противном случае это его собственное значение, иlabelЭто атрибут, переданный пользователем, представляющий значение, представленное самим радиокомпонентом.Трудность здесь в том, чтоthis.valueЧто это такое, посмотрите исходный код, чтобы узнатьthis.valueЯвляетсяprop, но у радиокомпонента на официальном сайте вообще нет этого значения для определения пользователя, которое реально используется на компонентеv-modelМетод описан на официальном сайте следующим образом
v-bind:valueэта опора,Следовательно, в радиокомпоненте должно быть объявлено свойство value, чтобы можно было получить значение определяемой пользователем v-модели., чтобы его можно было использовать, иsetметод должен пройтиthis.$emit('input', val)Запустите событие oninput в родительском компоненте, чтобы передать новое значение,dispatchМы обсудим это позже
Тогда эти слова
role="radio"
:aria-checked="model === label"
:aria-disabled="isDisabled"
Эти предложения используются для предоставления функций для неудобных людей, таких как программы чтения с экрана.Роль роли заключается в описании фактической роли нестандартного тега. Например, если вы используете div в качестве кнопки, то установите для div role="button", и вспомогательные инструменты смогут распознать, что это на самом деле кнопка. aria означает доступное многофункциональное интернет-приложение, а роль aria-* заключается в описании конкретной информации этого тега в визуальном контексте. Например:
<div role="checkbox" aria-checked="checked"></div>
Вспомогательные инструменты будут знать, что этот div на самом деле является ролью флажка, который установлен, а затем
:tabindex="tabIndex"
@keydown.space.stop.prevent="model = isDisabled ? model : label"
Tabindex указывает порядок, в котором элемент получает фокус при нажатии клавиши табуляции, которая также является вычисляемым свойством.
tabIndex() {
return !this.isDisabled ? (this.isGroup ? (this.model === this.label ? 0 : -1) : 0) : -1;
}
Если он отключен, а tabindex равен -1, вы не можете использовать клавишу табуляции, чтобы элемент получил фокус.Если он не отключен, если переключатель находится в компоненте группы переключателей и выбран, его можно получить с помощью фокус клавиши табуляции, иначе фокус не может быть получен через клавишу табуляции, Когда элементы с tabindex > 0 переключаются, элементы с tabindex = 0 будут переключаться, и они будут переключаться в порядке появления.Логика здесь заключается в том, что вкладка может получить доступ к переключателю только в выбранном состоянии.
последнее предложение@keydown.space.stop.prevent="model = isDisabled ? model : label"Я не знаю, что он делает.Я могу нормально использовать компонент после его удаления.Это означает, что нажатие пробела изменит значение модели? ? ?
варианты миксина
Обратите внимание на часть jsmixin:[Emitter], впервые представившие миксины, миксины — это очень гибкий способ распространения повторно используемой функциональности в компонентах Vue. Объекты Mixin могут содержать произвольные параметры компонента. Когда компонент использует миксин, все параметры миксина будут смешаны с собственными параметрами компонента. здесь будетEmitterСмешанный с компонентом, то есть все компоненты имеютEmitterМетод в миксине представляет собой массив, давайте зайдем в emitter.js, чтобы посмотреть, что смешано?
export default {
methods: {
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
Очевидно, здесь будетmethodsсмешанный, добавленныйdispatchа такжеbroadcastметод, то почему бы не написать эти два метода прямо в методах компонента? Причина в том, что это приведет к увеличению объема кода. Из-за общедоступных методов, которые используются во многих местах, смешивание методов может уменьшить объем кода и добиться повторного использования кода. Например, если есть 10 компонентов, которым требуется чтобы использовать эти два метода, затем используйте метод смешивания для каждого. Гораздо проще написать только одну строку кода для каждого компонента.
Смешанные методы будут объединены с исходными методами компонента.В случае конфликта методы в методах компонента будут сохранены, а затем мы изучимdispatchметод, реализующийЛогика отправки событий ближайшему конкретному родительскому компоненту, первый параметр - это имя родительского компонента, второй - имя события, а третий параметр - параметр события, который представляет собой массив или одиночное значение.Логика тоже очень проста: постоянно получать свой родительский компонент , судить Является ли это целевым компонентом, если нет, продолжайте переходить к его родительскому компоненту, чтобы судить, если да, вызывайте его на родительском компоненте$emitТриггерные события, обратите внимание сюда
parent.$emit.apply(parent, [eventName].concat(params));
нельзя записать как
parent.$emit(eventName,...params)
должен быть установлен с применением$emitЦелевой объект вызова, поскольку событие запускается в родительском компоненте, а не в диспетчере, здесь вы можете сказатьparent.$emitРазве он не вызывается в родительском компоненте? Не совсем,parent.$emitЯ только что получил метод emit, и он не объяснил, где его вызывать!Обратите особое внимание здесь
Тогда давайте посмотрим, где он используетсяdispatchметод, ответ в методах радиодетали
methods: {
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model);
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
Здесь handleChange привязан к вводу внутри радиокомпонента и срабатывает, когда радиокнопка теряет фокус.
<input @change="handleChange" .../>
Когда нажимается другая радиокнопка, запускается собственное событие onchange кнопки, и событие изменения передается здесь родителю, потому что радиокомпоненту требуется@changeчтобы проиллюстрировать событие, которое запускается при изменении связанного значения, иthis.modelЗначение передается, чтобы позволить пользователю получить значение, следующий код
<el-radio v-model="v" label='1' @change="radioChange"></el-radio>
Затем, если радиокомпонент находится в компоненте радиогруппы, он отправит событие handleChange, подобное компоненту радиогруппы, чтобы сообщить родительскому компоненту: мое значение изменилось! Иначе как уведомить родительский компонент о собственном значении!
Наконец это$nextTick, Это очень тонко, попробуйте удалить nextTick и обнаружите, что после того, как радиокомпонент щелкает новый компонент, напечатанное значение является значением старого компонента, что является проблемой.$nextTickрользаключается в отсрочке обратного вызова до следующего цикла обновления DOM., но почему вы можете получить значение только что нажатого радиокомпонента после добавления nextTick? ? ? Я не понимаю, надеюсь, кто-нибудь объяснит~