Bootstrap

Elemtn-ui radio源码

Element-ui radio

剖析

<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是否相等
;