内容简介:复选框的逻辑比单选框更为复杂,代码量也更多,这里只介绍其与单选框不同的逻辑,其余的分析参考单选框是不是看的一脸懵逼,最好是打开官网,对照checkbox用法一项项来分析其原理同单选框类似,复选框的示意图如下,无非就是左右2部分组成,外层套一个label,并隐藏原生的
复选框的逻辑比单选框更为复杂,代码量也更多,这里只介绍其与单选框不同的逻辑,其余的分析参考单选框
先上代码,官网代码 点此<template> <label class="el-checkbox" :class="[ border && checkboxSize ? 'el-checkbox--' + checkboxSize : '', { 'is-disabled': isDisabled }, { 'is-bordered': border }, { 'is-checked': isChecked } ]" role="checkbox" :aria-checked="indeterminate ? 'mixed': isChecked" :aria-disabled="isDisabled" :id="id" > <span class="el-checkbox__input" :class="{ 'is-disabled': isDisabled, 'is-checked': isChecked, 'is-indeterminate': indeterminate, 'is-focus': focus }" aria-checked="mixed" > <span class="el-checkbox__inner"></span> <input v-if="trueLabel || falseLabel" class="el-checkbox__original" type="checkbox" aria-hidden="true" :name="name" :disabled="isDisabled" :true-value="trueLabel" :false-value="falseLabel" v-model="model" @change="handleChange" @focus="focus = true" @blur="focus = false"> <input v-else class="el-checkbox__original" type="checkbox" aria-hidden="true" :disabled="isDisabled" :value="label" :name="name" v-model="model" @change="handleChange" @focus="focus = true" @blur="focus = false"> </span> <span class="el-checkbox__label" v-if="$slots.default || label"> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label> </template> <script> import Emitter from 'element-ui/src/mixins/emitter'; export default { name: 'ElCheckbox', mixins: [Emitter], inject: { elForm: { default: '' }, elFormItem: { default: '' } }, componentName: 'ElCheckbox', data() { return { selfModel: false, focus: false, isLimitExceeded: false }; }, computed: { model: { get() { return this.isGroup ? this.store : this.value !== undefined ? this.value : this.selfModel; }, set(val) { if (this.isGroup) { this.isLimitExceeded = false; (this._checkboxGroup.min !== undefined && val.length < this._checkboxGroup.min && (this.isLimitExceeded = true)); (this._checkboxGroup.max !== undefined && val.length > this._checkboxGroup.max && (this.isLimitExceeded = true)); this.isLimitExceeded === false && this.dispatch('ElCheckboxGroup', 'input', [val]); } else { this.$emit('input', val); this.selfModel = val; } } }, isChecked() { if ({}.toString.call(this.model) === '[object Boolean]') { return this.model; } else if (Array.isArray(this.model)) { return this.model.indexOf(this.label) > -1; } else if (this.model !== null && this.model !== undefined) { return this.model === this.trueLabel; } }, isGroup() { let parent = this.$parent; while (parent) { if (parent.$options.componentName !== 'ElCheckboxGroup') { parent = parent.$parent; } else { this._checkboxGroup = parent; return true; } } return false; }, store() { return this._checkboxGroup ? this._checkboxGroup.value : this.value; }, isDisabled() { return this.isGroup ? this._checkboxGroup.disabled || this.disabled || (this.elForm || {}).disabled : this.disabled || (this.elForm || {}).disabled; }, _elFormItemSize() { return (this.elFormItem || {}).elFormItemSize; }, checkboxSize() { const temCheckboxSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; return this.isGroup ? this._checkboxGroup.checkboxGroupSize || temCheckboxSize : temCheckboxSize; } }, props: { value: {}, label: {}, indeterminate: Boolean, disabled: Boolean, checked: Boolean, name: String, trueLabel: [String, Number], falseLabel: [String, Number], id: String, /* 当indeterminate为真时,为controls提供相关连的checkbox的id,表明元素间的控制关系*/ controls: String, /* 当indeterminate为真时,为controls提供相关连的checkbox的id,表明元素间的控制关系*/ border: Boolean, size: String }, methods: { addToStore() { if ( Array.isArray(this.model) && this.model.indexOf(this.label) === -1 ) { this.model.push(this.label); } else { this.model = this.trueLabel || true; } }, handleChange(ev) { if (this.isLimitExceeded) return; let value; if (ev.target.checked) { value = this.trueLabel === undefined ? true : this.trueLabel; } else { value = this.falseLabel === undefined ? false : this.falseLabel; } this.$emit('change', value, ev); this.$nextTick(() => { if (this.isGroup) { this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]); } }); } }, created() { this.checked && this.addToStore(); }, mounted() { // 为indeterminate元素 添加aria-controls 属性 if (this.indeterminate) { this.$el.setAttribute('aria-controls', this.controls); } }, watch: { value(value) { this.dispatch('ElFormItem', 'el.form.change', value); } } }; </script> 复制代码
是不是看的一脸懵逼,最好是打开官网,对照checkbox用法一项项来分析其原理
复选框整体html结构
同单选框类似,复选框的示意图如下,无非就是左右2部分组成,外层套一个label,并隐藏原生的 <input type='checkbox'>
简化的html结构如下所示
<label ...> <span class='el-checkbox__input'> <span class='el-checkbox__inner'></span> <input type='checkbox' .../> </span> <span class='el-checkbox__label'> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label> 复制代码
这里具体参考上一篇单选按钮的文章,重点说下上图的蓝色方框内的勾是怎么实现的,也就是选中状态,开始我以为是一个类似Icon的东西,然而并不是,查看css代码如下
&::after { box-sizing: content-box; content: ""; border: 1px solid $--checkbox-checked-icon-color; border-left: 0; border-top: 0; height: 7px; left: 4px; position: absolute; top: 1px; transform: rotate(45deg) scaleY(0); width: 3px; transition: transform .15s ease-in .05s; transform-origin: center; } 复制代码
很明显,这是 el-checkbox__inner
类的after伪元素,里面是一个只有右下border的长方形经过旋转45度后的图形,也就是一个勾的形状,所以这个勾只是纯粹的css实现而已,好处是简化了html结构,并且还用了 transition
来添加点击后勾变大的动画效果,这里是通过过渡 transform
的 scaleY
的值来实现,未选中时 scaleY
为0,选中时为1,就实现了勾放大的效果
因此要善用伪元素,会简化很多不必要的代码
Vue中的复选框原理
首先来看Vue中的复选框是怎么实现的,了解这个有助于理解Element的实现,官网介绍如下
上图中单个复选框使用bool值,多个复选框使用数组即可,这里其实Vue在幕后做了许多工作,找到Vue中相关源码如下
function genCheckboxModel ( el: ASTElement, value: string, modifiers: ?ASTModifiers ) { const number = modifiers && modifiers.number const valueBinding = getBindingAttr(el, 'value') || 'null' const trueValueBinding = getBindingAttr(el, 'true-value') || 'true' const falseValueBinding = getBindingAttr(el, 'false-value') || 'false' addProp(el, 'checked', `Array.isArray(${value})` + `?_i(${value},${valueBinding})>-1` + ( trueValueBinding === 'true' ? `:(${value})` : `:_q(${value},${trueValueBinding})` ) ) addHandler(el, 'change', `var $$a=${value},` + '$$el=$event.target,' + `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` + 'if(Array.isArray($$a)){' + `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` + '$$i=_i($$a,$$v);' + `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` + `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` + `}else{${genAssignmentCode(value, '$$c')}}`, null, true ) } 复制代码
这就是处理checkbox的v-model的代码,我们只需要知道这段代码大概在做啥就行,细节不用太清楚,代码中首先获取 v:bind
绑定的value的值,也就是下面示例代码中的 Jack
(注意代码中其实处理了不是v:bind的情况,具体看源码),然后 genCheckboxModel
这个函数的参数中的 value
就是v-model的值,也就是下面的 checkedNames
,接下来 addProp
方法的逻辑:如果 checkedNames
是数组,则通过indexOf查询 Jack
是否在 checkedNames
中,如果在则给input添加checked属性代表被选中,其次如果 checkedNames
不是数组,则直接比较2者是否相等来决定是否给input添加checked属性
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
由上面的分析可见, 复选框的选中的checked属性是Vue幕后添加的,通过值的比较来决定是否添加该属性
然后来看 addHandler
方法,这个方法给复选框添加了change事件,原生复选框点击后它的checked属性会改变(true或false),但是Vue中的 checkedNames
的值会跟着变化,这里就是 addHandler
所做的工作了,该方法里面总体逻辑就是首先判断 checkedNames
是否是数组,如果是且该复选框被选中,则将该复选框的值加入 checkedNames
数组中,如果该复选框没有被选中,则从数组中去掉它(注意这里没有用splice,而是2个slice后concat合并成一个数组,splice会改变原始数组,这样就不会)
所以onchange这个事件也是Vue幕后处理的,因此 checkedNames
数组就能够随着我们点击不同的复选框而同步变化
checkbox源码分析
接下来我们按官网罗列的功能依次分析
禁用功能
禁用功能最简单,使用起来如下代码,只需添加 disabled
属性即可
<el-checkbox v-model="checked1" disabled>备选项1</el-checkbox> 复制代码
源码里对应的 :disabled
属性
<input v-else class="el-checkbox__original" type="checkbox" aria-hidden="true" :disabled="isDisabled" :value="label" :name="name" v-model="model" @change="handleChange" @focus="focus = true" @blur="focus = false" > 复制代码
,这里 isDisabled
是个计算属性,因为要考虑到复选框组组件的存在,复选框组组件 <el-checkbox-group>
也有 disabled
属性,且复选框组组件是复选框组件的父级组件
isDisabled() { return this.isGroup ? this._checkboxGroup.disabled || this.disabled || (this.elForm || {}).disabled : this.disabled || (this.elForm || {}).disabled; }, 复制代码
这里首先判断自己是不是被包含在复选框组组件内,如果是的话那么禁用属性就是父级的复选框组组件的禁用属性,否则就是自己的属性,关于如何判断是否被包含在复选框组组件内,前面系列文章已经介绍过了
多选框组组件
这里翻看Vue官网,示例代码说明 仅仅需要把多个复选框的input的v-model设置为同一个数组就能达到复选框组的目的
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> 复制代码
但是查看Element的代码,还单独抽象出了一个 <el-checkbox-group>
组件,这样做的好处在于不用给每个复选框组件设置v-model,只需给 <el-checkbox-group>
设置v-model即可,且这个抽象出的组件还能添加其他很多自定义的属性,相当于添加一个父组件统一控制所有子复选框的某些行为
<el-checkbox-group>
的代码很简单,html部分如下
<template> <div class="el-checkbox-group" role="group" aria-label="checkbox-group"> <slot></slot> </div> </template> 复制代码
就是一个div里面放置了一个插槽,插槽的内容就是用户放进去的 <el-checkbox>
组件,多选框框组组件的props如下
props: { value: {}, disabled: Boolean, min: Number, max: Number, size: String, fill: String, textColor: String }, 复制代码
其中 value
是组件上v-model的用法,具体参考官网和前面文章的说明,这里的 min,max
属性控制了复选框框组最多和最少能选择的复选框数量,这是怎么实现的呢?
首先查看源码注意到,这里的逻辑并没有放在 <el-checkbox-group>
里实现,而是放在 <el-checkbox>
里实现,因为你实际点击的是 <el-checkbox>
的input,所以需要在复选框组件内实现逻辑,相关代码如下
model: { get() { return this.isGroup ? this.store : this.value !== undefined ? this.value : this.selfModel; }, set(val) { if (this.isGroup) { this.isLimitExceeded = false; (this._checkboxGroup.min !== undefined && val.length < this._checkboxGroup.min && (this.isLimitExceeded = true)); (this._checkboxGroup.max !== undefined && val.length > this._checkboxGroup.max && (this.isLimitExceeded = true)); this.isLimitExceeded === false && this.dispatch('ElCheckboxGroup', 'input', [val]); } else { this.$emit('input', val); this.selfModel = val; } } }, 复制代码
这个model是计算属性,在input里面的 v-model="model"
处使用,代表复选框组件v-model的值,计算属性的get,set用法参考官网,先看get,首先判断是否被包含在复选框组组件内,如果是的话,model的值就等于 this.store
,这个store也是个计算属性,如下
store() { return this._checkboxGroup ? this._checkboxGroup.value : this.value; }, 复制代码
它也要判断是否被包含在复选框组组件内,如果是则返回复选框组组件的value,这个value就是下面示例代码中的checkList
<el-checkbox-group v-model="checkList"> 复制代码
因此这里就把用户传递进去的checkList这个数组给传递到了子 <el-checkbox>
内,而 this._checkboxGroup
是在 isGroup
这个计算属性中赋值的,它就是自己外层的 <el-checkbox-group>
组件
再来看set方法,set是给model赋值时触发的方法,会在用户点击复选框时触发复选框的onchange事件,在这个事件里面赋值,从而触发set方法,set方法里面用一个 isLimitExceeded
变量来判断是否超出max和min的限制,如果min属性存在,且val数组的长度小于min,则说明已经越界,此时设置 isLimitExceeded
为true,max同理,val的值是在Vue源码里处理的,这里不用深究,后面当 isLimitExceeded
为false时也就是未越界时才用dispatch通知父组件自己更新后的val的值
一个疑惑是在 <el-checkbox>
内的input上绑定了 @change="handleChange"
,代码如下
handleChange(ev) { if (this.isLimitExceeded) return; let value; if (ev.target.checked) { value = this.trueLabel === undefined ? true : this.trueLabel; } else { value = this.falseLabel === undefined ? false : this.falseLabel; } this.$emit('change', value, ev); this.$nextTick(() => { if (this.isGroup) { this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]); } }); } 复制代码
上面的handleChange同样向父checkGroup组件dispatch了value,那么这个dispatch和上面的dispatch的区别在哪里呢?仔细分析后发现这里的dispatch仅仅是通知 <el-checkbox-group>
自己的值变化了,在 <el-checkbox-group>
上可以用@change来获取变化后的值(用户可以拿到该值进行进一步处理),而前面的dispatch则更新了 <el-checkbox-group>
的v-model属性的值,这2个dispatch的作用是不同的,请仔细理解
然后handleChange里 this.$emit('change', value, ev)
表示将value和ev原生事件对象传递给 <el-checkbox>
的onchange事件,因为用户可能需要这个接口来获取更新后的数据
最后再来看看当选中复选框时,css样式变化的逻辑
<span class="el-checkbox__input" :class="{ 'is-disabled': isDisabled, 'is-checked': isChecked, 'is-indeterminate': indeterminate, 'is-focus': focus }" aria-checked="mixed" > 复制代码
这个span代表模拟的复选框按钮,其中 is-checked
类代表选中时的样式类,这个类由 isChecked
控制,这是个计算属性,代码如下
isChecked() { if ({}.toString.call(this.model) === '[object Boolean]') { return this.model; } else if (Array.isArray(this.model)) { return this.model.indexOf(this.label) > -1; } else if (this.model !== null && this.model !== undefined) { return this.model === this.trueLabel; } }, 复制代码
第一步判断 this.model
是不是bool类型,注意这里的判断方法, Object.prototype.toString.call
来判断才是最可靠的,当model是bool时说明这个值就控制这个复选框他自己,如果这个model是数组,则判断label在不在该数组中,如果在则表示选中了该复选框,从而 isChecked
为true,label是用户定义在复选框上的属性,代表该复选框的值,具体看官网
主要内容差不多这么多,其实还有很多细节没写完,具体可以参考源码啦
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
社群营销实战手册
秋叶、邻三月、秦阳 / 人民邮电出版社 / 2018-1 / 69.00元
互联网正从“物以类聚”,走向“人以群分”的时代。秋叶等人的“社群营销”,并非单纯靠社群卖东西,而是建立一种中心化的、自行运转的生态,让“同好”们形成紧密的联系,创造出海量营销机会。 《社群营销实战手册 从社群运营到社群经济》共5章内容,从社群的定位、建立、扩张、变现、运营,到社群的生命周期延长、社群运营团队的打造和管理以及社群管理工具,大量干货秘笈一应俱全,并提供丰富的运营实战案例,全面解读社群的......一起来看看 《社群营销实战手册》 这本书的介绍吧!