Element-UI 源码简析——Radio单选框(上)

2,063 阅读4分钟

26d8116a7588ac4a91a8398280e436fe.png

序言

本章就来分享一下Element-ui 源码的Radio篇,文章的节奏是按照正常的Element目录结构来分享,首先就是Form里面的组件。这样的话第一个就是Radio了

组件流程

  1. 组件代码从 0 到 1 的过程
  2. 对应的组件里面index引入install方法并抛出
  3. main.js 集体引入
  4. 使用Vue.use方法注册
  5. 最后页面中使用

最后呢 就是源码的位置了:packages/radio/src/radio.vue。

radio文件夹下有一个index.js用于注册组件然后抛出,src是用来放组件代码的,但是这radio的组件代码相比于上次的Button就比较多了很多,radio.vue 、 radio-button.vue 、radio-group.vue,如果经常用Element的童鞋们大致的看到这些文件的名称应该也能猜到个七七八八了。

结构分析

一、分析的话 我们从每个vue文件的三个方面着手:DOM结构数据属性事件

二、Element针对于radio做了三大类的工作分别是 基础用法单选框组按钮样式 根据上面的vue文件可以一一对应.

radio基础用法

DOM结构

    <label>
        <span>
            <span></span>
            <input/>
        </span>
        <span>
            <slot></slot>
            <template v-if="!$slots.default">{{ label }}</template>
        </span>
    </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"
    >
        <span
            class="el-radio__input"
            :class="{
                'is-disabled': isDisabled,
                'is-checked': model === label,
            }"
        >
            <span class="el-radio__inner"></span>
            <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>

看到这后会有一个疑问? 为什么Radio不单单的是一个input标签,为什么需要label来包裹一下, label标签顾名思义是展示类的标签,但是他还是有一个另外的作用!

在单独的input框中,用户想要输入内容时只能点击输入框才可以使输入框获取焦点。

< inpust id="account" name="account" >

如果在input的周围加一个label,那么点击label显示的文字时,焦点也会自动集中在输入框中。具体的使用方法是label的for=“输入框的id”

< label for="account">账号< /label > < input id="account" name="account"/ >

476a1caba12ffe6a7eca923e3f30a564.png

在点击label的时候触发了两次操作,一次是label的点击,一次是input自动获取焦点。

label
  • class="el-radio" : 单选样式

  • class : 针对于样式的

    --border && radioSize ? 'el-radio--' + radioSize : '' : radio大小 Radio 的尺寸,仅在 border 为真时有效

    --is-disabled': isDisabled : 禁用

    --is-focus': focus : 选中的样式

    --is-bordered': border : 边框

    --is-checked': model === label : 按钮是否被选中

  • role="radio" : 无障碍网页应用属性

    --:aria-checked="model === label"

    --:aria-disabled="isDisabled"

      无障碍网页应用。主要针对的是视觉缺陷,失聪,行动不便的残疾人以及假装残疾的测试人员。
      尤其像盲人,眼睛看不到,其浏览网页则需要借助辅助设备,如屏幕阅读器,屏幕阅读机可以大声朗读或者输出盲文。
      而ARIA就是可以让屏幕阅读器准确识别网页中的内容,变化,状态的技术规范,可以让盲人这类用户也能无障碍阅读! 
    

    WAI-ARIA无障碍网页应用属性完全展示地址

  • :tabindex="tabIndex" : tab键获取焦点 -1 代表不可选, 0 代表可选

  • @keydown.space.stop.prevent="model = isDisabled ? model : label" :

    keydown.space 按下空格

    stop 停止冒泡

    prevent 阻止默认行为

    当tab选中当前 radio 在键盘上敲击空格键的时候(space即空格键 ),阻止了原生事件发生

label > span
  • class="el-radio__input" : 单选样式

  • class : --is-disabled: isDisabled, 禁用 --is-checked': model === label :是否选中

label > input
  • ref="radio" : 注册ref

  • class="el-radio__original" : opacity: 0 对其隐藏!!

  • :value="label" : Radio的value

  • type="radio" : input类型

  • aria-hidden="true" : 1.让这个元素对浏览器隐藏 2.为了避免屏幕识读设备抓取非故意的和可能产生混淆的输出内容

  • v-model="model" : 绑定值

  • @change="handleChange" : 原生change事件

  • :name="name" : 多个相同name属性为一组

  • :disabled="isDisabled" : 禁用

  • tabindex="-1" : 禁止tab选中

注意:如果使用-1值时,onfocus与onblur事件仍被启动

    知识点:为啥不用原生 input 而是自己模拟
    1.原生标签 radio不同浏览器样式不同,所以自己写来代替
    2.需要用到原生的 radio来获取焦点来触发 change事件,所以 element是 采用了 绝对定位老脱离文档流,
    以及设置透明度为0,而不是采用 dispaly:none或者 visibility:hidden,因为这样就无法点击了
    

数据属性

    name: "ElRadio",
    mixins: [Emitter],
    inject: { // 与 Form 表单相关 这里也不说了 暂且没有用到
        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) {
            // 向上循环找它爸爸,当能找到父元素组件名是ElRadioGroup的,则返回true,否则false
                if (parent.$options.componentName !== "ElRadioGroup") {
                    parent = parent.$parent;
                } else {
                   //若是radio group,保存group组件的引用
                    this._radioGroup = parent;
                    return true;
                }
            }
            return false;
        },
        model: {
            get() {
                // 如果是 radio-group 则取 radio-group 上面的值,否则则取 radio 本身的值
                return this.isGroup ? this._radioGroup.value : this.value;
            },
            set(val) {
                if (this.isGroup) {
                    this.dispatch("ElRadioGroup", "input", [val]);
                } else {
                    this.$emit("input", val);
                }
                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() {
             //禁用状态或者单选按钮是在单选框组组件内且不是选中状态时,返回 -1;
            return this.isDisabled || (this.isGroup && this.model !== this.label) ? -1 : 0;
        }
    }

computed

  • isGroup(): 判断是否存在radio group

  • model :

  • _elFormItemSize : 获取fromItem的大小

  • radioSize : 控制radio的大小

  • isDisabled : 该组件是否是禁用状态的

  • tabIndex : 用于设置组件的tab-index值

事件

     handleChange() {
        this.$nextTick(() => {
            // 触发实例上面的change事件
            this.$emit("change", this.model);
            // group的情况下,触发名为ElRadioGroup的父组件下的handleChange事件,也将当前选中值作为参数
            this.isGroup && this.dispatch("ElRadioGroup", "handleChange", this.model);
        });
    },

因为Radio比较多 所以分两篇来写,下一篇就是radio-group、radio-button。

招聘广告

言重式招聘 寻人!!!寻志同道合之人、寻竭忠尽智之人、寻深思远虑之人、寻勤恳至诚之人
浙江大华技术股份有限公司-软研-智慧城市产品研发部招聘高级前端!!!!!
欢迎大家来聊,有意向可发送简历到chen_zhen@dahuatech.com

芸芸众生,孰不爱生 ? 爱生之极,进而爱群 ---- 无奖竞猜