element-ui源码解析——你不知道的el-time-picker
1.源自一个小小的需求变动
小编写的页面中有一个时间字段,原来是 HH:mm:ss (时分秒)的形式,现在要改成只有小时,不要分秒。如下图所示:
小编想想了像这还不简单,看看文档,控制一下组件显示的形式以及点击确定按钮后得到的值的形式。于是乎,看看element-ui的文档,el-time-picker 中有控制显示格式的value-format属性
2.尝试修改
修改之前的代码:
<el-time-picker
is-range
value-format="HH:mm:ss"
v-model="timeValue"
@change="changeTime"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围">
</el-time-picker>
修改之后的代码:
<el-time-picker
is-range
value-format="HH"
v-model="timeValue"
@change="changeTime"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围">
</el-time-picker>
这样修改之后,虽然值的形式改变了,如下图只显示小时:
但是时间弹窗中还显示分秒:
网上资料说要把format属性也限制一下,TimePicker并没有介绍format属性,但是DatePicker介绍了format属性:
<el-time-picker
is-range
format="HH"
value-format="HH"
v-model="timeValue"
@change="changeTime"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围">
</el-time-picker>
这样修改之后,显示效果如下,秒对应的列去掉了,可是分钟对应的列还在
到底怎么回事儿?去看看element的源码吧!
3.查看element-ui 中time-picker的源码
3.1 TimePicker 源码
3.1.1 time-picker模块拿来主义,实际是date-picker中的内容
time-picker 源码的位置在:element-ui\packages\time-picker,内容如下:
import TimePicker from '../date-picker/src/picker/time-picker';
/* istanbul ignore next */
TimePicker.install = function(Vue) {
Vue.component(TimePicker.name, TimePicker);
};
export default TimePicker;
可以看到time-picker实际使用的是date-picker中的time-picker组件。真正的源码位置在 element-ui\packages\date-picker\src\picker\time-picker.js, 内容如下:
import Picker from '../picker';
import TimePanel from '../panel/time';
import TimeRangePanel from '../panel/time-range';
export default {
mixins: [Picker],
name: 'ElTimePicker',
props: {
isRange: Boolean,
arrowControl: Boolean
},
data() {
return {
type: ''
};
},
watch: {
isRange(isRange) {
if (this.picker) {
this.unmountPicker();
this.type = isRange ? 'timerange' : 'time';
this.panel = isRange ? TimeRangePanel : TimePanel;
this.mountPicker();
} else {
this.type = isRange ? 'timerange' : 'time';
this.panel = isRange ? TimeRangePanel : TimePanel;
}
}
},
created() {
this.type = this.isRange ? 'timerange' : 'time';
this.panel = this.isRange ? TimeRangePanel : TimePanel;
}
};
这里主要是引入了Picker, TimePanel, TimeRangePanel。那么它们都是什么呢?看下面的图就明白了:
有一点值得注意,就是这里把Picker混入了!因为我们在日常开发的时候混入的都是一个js文件,而这里混入的是vue文件。
3.1.2 time-picker中混入的用法
element-ui中混入的用法:
我们日常开发的混入用法:
小编也仿照element-ui的方法写了一下:
test-page目录中三个文件,引用关系如下图所示
mixin.vue引用了testMixin.js, testMixin.js引用了MixinCom。三个文件的内容很简单,如下代码所示:
mixin.vue:
<template>
<Test></Test>
</template>
<script>
import Test from './testMixin'
export default {
components: {Test}
}
</script>
testMixin.js:
import MixinCom from './MixinCom.vue'
export default {
mixins: [MixinCom],
data() {
return {
a: 1,
b: 2
}
},
mounted() {
console.log('mixin',this)
}
}
MixinCom.vue:
<template>
<div>这是测试mixin页面222</div>
</template>
<script>
export default {
data() {
return {
test: ''
}
}
}
</script>
运行效果如下:
至于什么时候混入一个vue文件?这么混入有什么好处?vue内部是怎么处理混入的?留给大家一起探讨,欢迎在评论区留言!下面分别看看引入的三个组件的源码。
3.2 Picker源码
源码位置: element-ui\packages\date-picker\src\picker.vue
在picker.vue中有一段代码:
computed: {
ranged() {
return this.type.indexOf('range') > -1;
},
}
我们在使用组件的时候DatePicker组件的时候会传入type参数,其中有的参数中有range,如下入所示:
type中含有range的时候,就表示时间范围从 xxx至 xxx, 我们看一下模板部分的实现(只保留关键部分):
<template>
<!-- 一个时间 -->
<el-input
v-if="!ranged"
>
</el-input>
<div
v-else
>
<!-- 一个时间 -->
<input>
<!-- 分隔符 -->
<slot name="range-separator">
<span class="el-range-separator">{{ rangeSeparator }}</span>
</slot>
<!-- 另外一个时间 -->
<input>
</div>
</template>
下面看TimePanel的源码
3.3 TimePanel源码
element-ui\packages\date-picker\src\panel\time.vue
主要看模板部分
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-time-panel el-popper"
:class="popperClass">
<div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
<time-spinner
ref="spinner"
@change="handleChange"
:arrow-control="useArrow"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@select-range="setSelectionRange"
:date="date">
</time-spinner>
</div>
<div class="el-time-panel__footer">
<button
type="button"
class="el-time-panel__btn cancel"
@click="handleCancel">{{ t('el.datepicker.cancel') }}</button>
<button
type="button"
class="el-time-panel__btn"
:class="{confirm: !disabled}"
@click="handleConfirm()">{{ t('el.datepicker.confirm') }}</button>
</div>
</div>
</transition>
</template>
主要是引入了一个TimeSpinner组件,并且定义了取消和确定按钮,渲染后就是下面的样子:
下面看TimeRangePanel的源码
3.4 TimeRangePanel源码
node_modules\element-ui\packages\date-picker\src\panel\time-range.vue
我们看一下模板部分的实现(只保留关键部分)
<template>
<transition
name="el-zoom-in-top"
@after-leave="$emit('dodestroy')">
<divv-show="visible">
<div class="el-time-range-picker__content">
<div class="el-time-range-picker__cell">
<!-- 国际化的开始时间文言 -->
<div class="el-time-range-picker__header">{{ t('el.datepicker.startTime') }}</div>
<div>
<!-- 使用time-spinner 注意 showSeconds -->
<time-spinner
ref="minSpinner"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@change="handleMinChange"
:arrow-control="arrowControl"
@select-range="setMinSelectionRange"
:date="minDate">
</time-spinner>
</div>
</div>
<div class="el-time-range-picker__cell">
<!-- 国际化的结束时间文言 -->
<div class="el-time-range-picker__header">{{ t('el.datepicker.endTime') }}</div>
<div>
<!-- 使用time-spinner(属性略) -->
<time-spinner>
</time-spinner>
</div>
</div>
</div>
<div class="el-time-panel__footer">
<!-- 取消和确定按钮(省略) -->
</div>
</div>
</transition>
</template>
在布局上是左右布局,这里主要是使用了两次time-spinner组件,(注意 给time-spinner 传的showSeconds 属性,下文会提到)。time-spinne呈现如下图的效果:
最后看一下time-spinner组件:
3.5 TimeSpinne源码
element-ui\packages\date-picker\src\basic\time-spinner.vue (只保留关键部分):
<template>
<div class="el-time-spinner" :class="{ 'has-seconds': showSeconds }">
<template v-if="!arrowControl">
<!-- 小时列 -->
<el-scrollbar
tag="ul"
ref="hours">
<li
@click="handleClick('hours', { value: hour, disabled: disabled })"
v-for="(disabled, hour) in hoursList"
class="el-time-spinner__item"
:key="hour"
:class="{ 'active': hour === hours, 'disabled': disabled }">{{ ('0' + (amPmMode ? (hour % 12 || 12) : hour )).slice(-2) }}{{ amPm(hour) }}</li>
</el-scrollbar>
<!-- 分钟列 -->
<el-scrollbar
tag="ul"
ref="minutes">
<li
@click="handleClick('minutes', { value: key, disabled: false })"
v-for="(enabled, key) in minutesList"
:key="key"
class="el-time-spinner__item"
:class="{ 'active': key === minutes, disabled: !enabled }">{{ ('0' + key).slice(-2) }}</li>
</el-scrollbar>
<!-- 秒列 -->
<el-scrollbar
v-show="showSeconds"
ref="seconds">
<li
@click="handleClick('seconds', { value: key, disabled: false })"
v-for="(second, key) in 60"
class="el-time-spinner__item"
:class="{ 'active': key === seconds }"
:key="key">{{ ('0' + key).slice(-2) }}</li>
</el-scrollbar>
</template>
</div>
</template>
上面模板代码分别定义了小时列、分钟列和秒列,注意在秒列上有一个v-show="showSeconds",定义如下:
export default {
props: {
showSeconds: {
type: Boolean,
default: true
},
},
}
可以看到showSeconds是从父组件传来的属性,也就是从time-range.vue 传过来的。回到time-range.vue看一下其定义:
export default {
computed: {
showSeconds() {
return (this.format || '').indexOf('ss') !== -1;
},
}
}
showSecond是计算属性,由format计算得来,下面看看format:
data() {
return {
format: 'HH:mm:ss',
};
},
format为data中定义的数据,默认值为'HH:mm:ss'
综上,秒列是可以被隐藏掉的,而分钟列上没有定义showMinute,所以隐藏不掉,这也就是为什么尽管我给format和value-format属性传值为"HH"的时候仍然显示分钟。
4.总结
本文从工作中使用el-time-picker遇到的问题出发,简单研究了一下其源码,但也是颇有收获。
(1)TimePicker组件和DatePicker组件属于同一个家族,因此关键代码都放在了一起。TimePicker只是把DatePicker目录下用到的部分引入过来;
(2)TimePicker引入Picker并作为混入(mixin),让我开了眼界,原来不但可以混入js,也可以混入vue文件;
(3)TimePicker从UI的表现层面分成两部分:picker 和 panel。其中panel根据用户是否传入了带有range的type决定使用TimePanel还是TimeRangePanel ;
(4)无论是TimePanel还是TimeRangePanel ,最终使用的是time-spinner组件,而这个组件定义了时分秒三列,而只能隐藏秒列,不可以隐藏分钟列。
正所谓“上班用element-ui,下班用饿了么”,element-ui是前端vue开发的好帮手,相信你在使用过程中也会有心得和体会,可以说一说啊。
喜欢的话可以留言,点赞,在看,转发~, 喜欢小编文章的话也可以关注公众号“重温新知”查看更多内容~
上一篇: 简单易懂:原码、反码与补码的区别与应用