内容简介:复选框的逻辑比单选框更为复杂,代码量也更多,这里只介绍其与单选框不同的逻辑,其余的分析参考单选框是不是看的一脸懵逼,最好是打开官网,对照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使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Music Recommendation and Discovery
Òscar Celma / Springer / 2010-9-7 / USD 49.95
With so much more music available these days, traditional ways of finding music have diminished. Today radio shows are often programmed by large corporations that create playlists drawn from a limited......一起来看看 《Music Recommendation and Discovery》 这本书的介绍吧!