剖析
<template>
<el-radio v-model="radio" label="1">男</el-radio>
<el-radio v-model="radio" label="2">女</el-radio>
</template>
<script>
export default {
data () {
return {
radio: '1'
};
},
mounted() {
console.log('父组件v-model绑定的值radio发生了变化', this.radio);
// 上述两个单选框是公用一个v-model值radio,默认为1,此时radio与第一个label值相等,即默认第一个单选框选中,第二个单选框非选中。如果再点击第二个单选框,其input值发生变化,组件内部调用this.$emit('input', val),通知父组件radio值更改为2,(vue中的data是响应式更新),此时该radio与第一个label值不相等,则取消选中。
}
}
</script>
- 子组件 el-radio
template
<template>
<label
class="y-radio"
:class="[
{'is-focus': focus},
{'is-checked': model === label}
]"
role="radio"
:aria-checked="model === label">
<!-- 分两部分, 一部分是原点, 一部分是标签名 -->
<span
class="y-radio__input"
:class="{
'is-checked': model === label
}">
<!-- 展示原点, 通过原生的input操作,y-radio__inner对应的标签覆盖原生的展示
如果不想要原生的单选框, 则使用相对定位和绝对定位
由于v-mdoel机制, 对于单选框相当于
checkbox 和 radio 使用 checked property 和 change 事件
change事件用于更新model值
未知 @change="handleChange"的存在意义?--组件上的方法,选中状态发生变化后的回调函数
-->
<span class="y-radio__inner"></span>
<!-- 原生input单选框 这里v-model="model" <=> 绑定value和change事件,这里使原生radio隐藏但仍可以操作-->
<input
ref="radio"
class="y-radio__original"
:value="label"
type="radio"
aria-hidden="false"
v-model="model"
@focus="focus = true"
@blur="focus = false"
@change="handleChange" />
</span>
<span class="y-radio__label">
<!-- vue插槽 v-if="$slots.default"-->
<!-- 如果有子数据,则展示子数据, 否则展示label -->
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
script
<script>
export default {
name: 'YRadio',
// 接收父组件传递的数据
props: {
value: {}, // 父组件v-model绑定的值
label: {}, // 父组件label
},
computed: {
/**
* 因为model值是绑定到原生的input框中的
* 只要该input框
*/
model: {
get() {
return this.value;
},
set(val) {
console.log('model值发生了变化', val);
// 通知父组件更新v-model对应的value值
this.$emit('input', val)
}
}
},
data() {
return {
focus: false, // focus为input是否聚焦
}
},
methods: {
handleChange() {
// input的值改变后, 需要等视图的更新, 更新后方可获取该值, 为label对应的值
this.$nextTick(()=>{
console.log('handleChange', this.model);
// 这句话有什么意义?, 调用父组件change事件,为选中状态改变后的回调函数
this.$emit('change', this.model);
})
},
},
mounted() {
console.log(this.$slots, Boolean(this.$slots.default), this.model)
}
}
</script>
style
y-radio {
position: relative;
display: inline-block;
white-space: nowrap;
margin-right: 30px;
}
.y-radio:last-child {
margin-right: 0;
}
.y-radio__input {
position: relative;
line-height: 1;
vertical-align: middle;
}
.y-radio__inner {
position: relative;
width: 14px;
height: 14px;
cursor: pointer;
box-sizing: border-box;
border-radius: 100%;
border: 1px solid #dcdfe6;
display: inline-block;
}
.y-radio__inner::after {
content: '';
width: 4px;
height: 4px;
box-sizing: border-box;
border-radius: 100%;
background-color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
transition: transform .15s ease-in;
}
/* 选中样式 */
.y-radio__input.is-checked .y-radio__inner {
background-color: #409eff;
border-color: #409eff;
}
.y-radio__input.is-checked .y-radio__inner::after {
transform: translate(-50%, -50%) scale(1);
}
/* 原生的radio隐藏但是仍可操作 opacity:0,z-index:-1 */
.y-radio__original {
opacity: 0;
outline: none;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
}
.y-radio__label {
padding-left: 10px;
}
.y-radio__inner:hover {
border-color: #409eff;
transition: border .15s ease-in-out;
}
- v-model 实现数据的双向绑定,这里相当于给组件传递了一个value属性和input事件(value值是父级组件数,input事件也是父级组件的,父级组件传递给子组件的数据,需要通过父组件更改,input事件作用为更改该vlue值)。
v-model="radio" <=> v-bind:value="radio" v-on:input="newVal => {this.radio = newVal}""
- label,子组件判断是否选中便是比较父组件传过来的value与label是否相等