radio.vue
<template>
<!-- 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"
>
<!-- 按钮部分 el-radio__input控制样式-->
<span class="el-radio__input"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<!-- el-radio__inner 控制形状和样式 -->
<span class="el-radio__inner"></span>
<!-- input来控制交互
@focus="focus = true"
@blur="focus = false"
通过focus和blur来改变focus的值
input的值就是label
-->
<input
ref="radio"
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;
},
// 当我们点击单选框的时候会触发这个set方法 从而将label的值传给父组件并改变父组件v-model绑定的值
// 从而触发组件的重新渲染 而此时model的值也就改变了 样式也就切换了
model: {
get() {
// this.value 是指的父组件传进来的props value值 也就是我们的v-model绑定的
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
// 这里的val实际上就是this.label
// 如果是调用了el-radio-group
if (this.isGroup) {
this.dispatch('ElRadioGroup', 'input', [val]);
} else {
// 调用父组件的@input 并将传进来的label传给父组件
/**
* 父组件
* <el-radio v-model="radio" label="1">备选项</el-radio>
* 当我们点击input时就会执行以下@input赋值操作
* 这里的v-model='radio' 其实就相当于@input = "(val)=>radio=val" :value='radio'
* */
this.$emit('input', val);
}
// 重置input的状态 this.model就是this.value
this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);
}
},
_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)) ? -1 : 0;
}
},
methods: {
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model);
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
};
</script>
mixins中
Emitter.js
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
// ElRadioGroup/input/[3]
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 找到与componentName匹配的祖先组件
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
// 这里不是很懂 为啥是parent.$emit 而不是parent的最近childen
/**
* 其实这里涉及到一个.vue文件定义和组件调用的关系了
* 这里的parent 其实是该组件.vue定义的时候而parent.$emit其实是相当于在定义的地方去调用该组件被调用时
* 的v-model(@input)或者是其他方法
*
* */
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
其实这里的关键还是v-model的理解 它实际上是@input=(val)=>xxx=val;:value=’xxx’ 的简写
为什么我们点击单选按钮会触发计算属性的set方法 就是这个原因,先调用了@input方法,修改了计算属性 然后再进入到set方法
这里需要注意的是 vue3中获取组件名的方式改变了 不再是componentName 而是name