上一篇博客中學習了項目的結(jié)構(gòu)爸邢,這篇博客來學幾個簡單的組件的實現(xiàn)。
在上一篇博客中我們提到了組件的源碼都是存放在 packages
目錄下的拿愧,所以我們從中挑一些組件來學習杠河。先從簡單的入手,來學習 button浇辜、radio券敌、checkbox和InputNumber這四個組件的源碼~
Button
找到 packages/button/
目錄下,先看看 index.js 做了什么柳洋?
import ElButton from './src/button';
// 注冊全局組件
ElButton.install = function(Vue) {
Vue.component(ElButton.name, ElButton);
};
export default ElButton;
其實就是獲取組件待诅,然后全局注冊組件。很好理解熊镣,我們自定義組件也經(jīng)常這么干卑雁∧际椋看看組件內(nèi)容吧~
<!-- packages/button/src/button.vue -->
<template>
<button
class="el-button"
@click="handleClick"
:disabled="disabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': disabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'ElButton',
// 獲取父級組件 provide 傳遞下來的數(shù)據(jù)。
inject: {
elFormItem: {
default: ''
}
},
// 屬性 http://element-cn.eleme.io/#/zh-CN/component/button
props: {
// 類型 primary / success / warning / danger / info / text
type: {
type: String,
default: 'default'
},
// 尺寸 medium / small / mini
size: String,
// 圖標類名
icon: {
type: String,
default: ''
},
// 原生type屬性 button / submit / reset
nativeType: {
type: String,
default: 'button'
},
// 是否加載中狀態(tài)
loading: Boolean,
// 是否禁用狀態(tài)
disabled: Boolean,
// 是否樸素按鈕
plain: Boolean,
// 是否默認聚焦
autofocus: Boolean,
// 是否圓形按鈕
round: Boolean
},
computed: {
// elFormItem 尺寸獲取
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// 按鈕尺寸計算
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
}
},
methods: {
// 點擊事件测蹲,使得組件的點擊事件為 @click莹捡,與原生點擊保持一致。
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
代碼已注釋~其實 script 部分很簡單:通過inject和props獲取數(shù)據(jù)扣甲,計算方法計算尺寸篮赢,事件處理點擊事件。關(guān)鍵點在于button中的幾個class琉挖。說白了荷逞,其實這就是個原生的button組件,只是樣式上有所不同粹排。
樣式文件都是存在 packages/theme-chalk/
目錄下的。所以 button 的樣式目錄位于 packages/theme-chalk/src/button.scss
涩澡。由于自己 CSS 非常渣顽耳,所以這步先跳過~
Radio
來看下Radio。嗯……目錄位置在 packages/radio/
目錄下妙同。index.js 文件和 button 是一樣的 —— 導入組件射富、定義全局注冊組件方法、導出粥帚。所以這邊來看看 radio.vue 文件胰耗。
<!-- packages/button/src/radio.vue -->
<template>
<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 = label"
>
<!-- radio 圖標部分 -->
<span class="el-radio__input"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<!-- 圖標效果 -->
<span class="el-radio__inner"></span>
<input
class="el-radio__original"
:value="label"
type="radio"
v-model="model"
@focus="focus = true"
@blur="focus = false"
@change="handleChange"
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
</span>
<!-- 文本內(nèi)容 -->
<span class="el-radio__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: 'ElRadio',
// 混合選項
mixins: [Emitter],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
componentName: 'ElRadio',
props: {
// value值
value: {},
// Radio 的 value
label: {},
// 是否禁用
disabled: Boolean,
// 原生 name 屬性
name: String,
// 是否顯示邊框
border: Boolean,
// Radio 的尺寸,僅在 border 為真時有效 medium / small / mini
size: String
},
data() {
return {
focus: false
};
},
computed: {
// 向上遍歷查詢父級組件是否有 ElRadioGroup芒涡,即是否在按鈕組中
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
// 重新定義 v-model 綁定內(nèi)容的 get 和 set
model: {
get() {
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
if (this.isGroup) {
this.dispatch('ElRadioGroup', 'input', [val]);
} else {
this.$emit('input', val);
}
}
},
// elFormItem 尺寸
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// 計算 radio 尺寸柴灯,用于顯示帶有邊框的radio的尺寸大小
radioSize() {
const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
return this.isGroup
? this._radioGroup.radioGroupSize || temRadioSize
: temRadioSize;
},
// 是否禁用,如果radioGroup禁用則按鈕禁用费尽。
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
// 標簽索引 0 or -1
tabIndex() {
return !this.isDisabled ? (this.isGroup ? (this.model === this.label ? 0 : -1) : 0) : -1;
}
},
methods: {
// 處理 @change 事件赠群,如果有按鈕組,出發(fā)按鈕組事件旱幼。
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model);
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
};
</script>
從代碼中可以看到查描,radio不止是個input那么簡單。后面還會顯示的文本內(nèi)容柏卤。script 部分的邏輯是:通過 inject 和 props 獲取傳參冬三,data記錄 radio 的 focus 狀態(tài)。compute 中計算一些屬性值缘缚;methods中處理onchange事件勾笆。radio 中多了些關(guān)于 radioGroup 的處理。具體我都寫在注釋中了桥滨。
另外要注意的還是 template 中的各類 class匠襟,radio的css文件想必也猜到了钝侠,文件位于 packages/theme-chalk/src/radio.scss
。到這里就會發(fā)現(xiàn):這些簡單組件主要是樣式上面的處理酸舍,對于邏輯上處理并不大帅韧。
在 radio 中導入了 element-ui/src/mixins/emitter.js
文件,來看看它的作用是什么啃勉?
// src/mixins/emitter.js
// 廣播
function broadcast(componentName, eventName, params) {
// 遍歷子組件
this.$children.forEach(child => {
// 組件名
var name = child.$options.componentName;
if (name === componentName) {
// 觸發(fā)事件
child.$emit.apply(child, [eventName].concat(params));
} else {
// 執(zhí)行broadcast方法
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
// 父級組件及其組件名
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 有父級組件 同時 沒有name 或者 name 不等于組件名
while (parent && (!name || name !== componentName)) {
// parent 向上獲取父級組件
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
// 觸發(fā) eventName 事件
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
由注釋可見忽舟,dispatch 方法向上獲取父級組件并觸發(fā) eventName 事件。broadcast 方法向下遍歷子組件觸發(fā) eventName 事件淮阐。
至于 Vue 的 mixins 屬性是干嘛的叮阅?
混入 (mixins) 是一種分發(fā) Vue 組件中可復用功能的非常靈活的方式∑兀混入對象可以包含任意組件選項浩姥。當組件使用混入對象時,所有混入對象的選項將被混入該組件本身的選項状您。
好啦勒叠,至于 radio 的 scss 解析?抱歉膏孟,暫時對scss不熟眯分,之后補上~本篇關(guān)鍵將邏輯吧。
checkbox
找到 checkbox 的項目目錄柒桑,index.js 邏輯是一樣的~所以只需看看 checkbox.vue 文件
<!-- packages/button/src/checkbox.vue -->
<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"
>
<!-- checkbox -->
<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>
<!-- 如果有 true-value 或者 false-value -->
<input
v-if="trueLabel || falseLabel"
class="el-checkbox__original"
type="checkbox"
: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"
:disabled="isDisabled"
:value="label"
:name="name"
v-model="model"
@change="handleChange"
@focus="focus = true"
@blur="focus = false">
</span>
<!-- 文本內(nèi)容 -->
<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',
// 混合 Emitter
mixins: [Emitter],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
componentName: 'ElCheckbox',
data() {
return {
// checkbox model
selfModel: false,
// 焦點
focus: false,
// 超過限制弊决?
isLimitExceeded: false
};
},
computed: {
model: {
// 獲取model值
get() {
return this.isGroup
? this.store : this.value !== undefined
? this.value : this.selfModel;
},
// 設置 selfModel
set(val) {
// checkbox group 的set邏輯處理
if (this.isGroup) {
// 處理 isLimitExceeded
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));
// 觸發(fā) ElCheckboxGroup 的 input 事件
this.isLimitExceeded === false &&
this.dispatch('ElCheckboxGroup', 'input', [val]);
} else {
// 觸發(fā)當前組件 input 事件
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;
},
// 判斷 group,checkbox 的 value 獲取
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;
},
// elFormItem 的尺寸
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// checkbox 尺寸魁淳,同樣需要有邊框才有效
checkboxSize() {
const temCheckboxSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
return this.isGroup
? this._checkboxGroup.checkboxGroupSize || temCheckboxSize
: temCheckboxSize;
}
},
props: {
// value值
value: {},
// 選中狀態(tài)的值(只有在checkbox-group或者綁定對象類型為array時有效)
label: {},
// 設置 indeterminate 狀態(tài)飘诗,只負責樣式控制
indeterminate: Boolean,
// 是否禁用
disabled: Boolean,
// 當前是否勾選
checked: Boolean,
// 原生 name 屬性
name: String,
// 選中時的值
trueLabel: [String, Number],
// 沒有選中時的值
falseLabel: [String, Number],
id: String, /* 當indeterminate為真時,為controls提供相關(guān)連的checkbox的id界逛,表明元素間的控制關(guān)系*/
controls: String, /* 當indeterminate為真時疚察,為controls提供相關(guān)連的checkbox的id,表明元素間的控制關(guān)系*/
// 是否顯示邊框
border: Boolean,
// Checkbox 的尺寸仇奶,僅在 border 為真時有效
size: String
},
methods: {
// 添加數(shù)據(jù)到model
addToStore() {
if (
Array.isArray(this.model) &&
this.model.indexOf(this.label) === -1
) {
this.model.push(this.label);
} else {
this.model = this.trueLabel || true;
}
},
// 處理 @change 事件貌嫡,如果是 group 要處理 group 的 change 事件。
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() {
// 如果 checked 為 true该溯,執(zhí)行 addToStore 方法
this.checked && this.addToStore();
},
mounted() { // 為indeterminate元素 添加aria-controls 屬性
if (this.indeterminate) {
this.$el.setAttribute('aria-controls', this.controls);
}
}
};
</script>
其實和radio邏輯差不多岛抄。從上面的內(nèi)容中已知 emitter.js 用于觸發(fā)子組件或父組件的事件,而參數(shù)上與radio也差不多狈茉。不同點有夫椭,在顯示checkbox時,如果有true-babel 或 false-babel 屬性和沒有這兩個屬性顯示的是不同的 checkbox(v-if,v-else)氯庆。其他都差不多蹭秋。
具體代碼注釋即可扰付。
InputNumber
<template>
<div
@dragstart.prevent
:class="[
'el-input-number',
inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
{ 'is-disabled': inputNumberDisabled },
{ 'is-without-controls': !controls },
{ 'is-controls-right': controlsAtRight }
]">
<!-- 減法 -->
<span
class="el-input-number__decrease"
role="button"
v-if="controls"
v-repeat-click="decrease"
:class="{'is-disabled': minDisabled}"
@keydown.enter="decrease">
<i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i>
</span>
<!-- 加法 -->
<span
class="el-input-number__increase"
role="button"
v-if="controls"
v-repeat-click="increase"
:class="{'is-disabled': maxDisabled}"
@keydown.enter="increase">
<i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i>
</span>
<!-- el-input 內(nèi)容 -->
<el-input
ref="input"
:value="currentValue"
:disabled="inputNumberDisabled"
:size="inputNumberSize"
:max="max"
:min="min"
:name="name"
:label="label"
@keydown.up.native.prevent="increase"
@keydown.down.native.prevent="decrease"
@blur="handleBlur"
@focus="handleFocus"
@change="handleInputChange">
<!-- 占位符模板 -->
<template slot="prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</template>
<template slot="append" v-if="$slots.append">
<slot name="append"></slot>
</template>
</el-input>
</div>
</template>
<script>
import ElInput from 'element-ui/packages/input';
import Focus from 'element-ui/src/mixins/focus';
import RepeatClick from 'element-ui/src/directives/repeat-click';
export default {
name: 'ElInputNumber',
// options 混合
mixins: [Focus('input')],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
// 自定義指令
directives: {
repeatClick: RepeatClick
},
components: {
ElInput
},
props: {
// 計數(shù)器步長
step: {
type: Number,
default: 1
},
// 設置計數(shù)器允許的最大值
max: {
type: Number,
default: Infinity
},
// 設置計數(shù)器允許的最小值
min: {
type: Number,
default: -Infinity
},
// 綁定值
value: {},
// 是否禁用計數(shù)器
disabled: Boolean,
// 計數(shù)器尺寸 large, small
size: String,
// 是否使用控制按鈕
controls: {
type: Boolean,
default: true
},
// 控制按鈕位置 right
controlsPosition: {
type: String,
default: ''
},
// 原生 name 屬性
name: String,
// 輸入框關(guān)聯(lián)的label文字
label: String
},
data() {
return {
// 當前值
currentValue: 0
};
},
watch: {
value: {
// 立即執(zhí)行 get()
immediate: true,
handler(value) {
let newVal = value === undefined ? value : Number(value);
if (newVal !== undefined && isNaN(newVal)) return;
if (newVal >= this.max) newVal = this.max;
if (newVal <= this.min) newVal = this.min;
this.currentValue = newVal;
// 觸發(fā) @input 事件
this.$emit('input', newVal);
}
}
},
computed: {
// 最小禁用,無法再減
minDisabled() {
return this._decrease(this.value, this.step) < this.min;
},
// 最大禁用仁讨,無法再加
maxDisabled() {
return this._increase(this.value, this.step) > this.max;
},
// 精度
precision() {
const { value, step, getPrecision } = this;
return Math.max(getPrecision(value), getPrecision(step));
},
// 按鈕是否要顯示于右側(cè)
controlsAtRight() {
return this.controlsPosition === 'right';
},
// FormItem尺寸
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// 計算尺寸
inputNumberSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
// 獲取禁用狀態(tài)
inputNumberDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
},
methods: {
// 計算精度
toPrecision(num, precision) {
if (precision === undefined) precision = this.precision;
return parseFloat(parseFloat(Number(num).toFixed(precision)));
},
// 獲取精度
getPrecision(value) {
if (value === undefined) return 0;
const valueString = value.toString();
const dotPosition = valueString.indexOf('.');
let precision = 0;
if (dotPosition !== -1) {
precision = valueString.length - dotPosition - 1;
}
return precision;
},
// 獲取加法后的精度
_increase(val, step) {
if (typeof val !== 'number' && val !== undefined) return this.currentValue;
const precisionFactor = Math.pow(10, this.precision);
// Solve the accuracy problem of JS decimal calculation by converting the value to integer.
return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor);
},
// 獲取減法后的精度
_decrease(val, step) {
if (typeof val !== 'number' && val !== undefined) return this.currentValue;
const precisionFactor = Math.pow(10, this.precision);
return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor);
},
// 加法行為
increase() {
if (this.inputNumberDisabled || this.maxDisabled) return;
const value = this.value || 0;
const newVal = this._increase(value, this.step);
this.setCurrentValue(newVal);
},
// 減法行為
decrease() {
if (this.inputNumberDisabled || this.minDisabled) return;
const value = this.value || 0;
const newVal = this._decrease(value, this.step);
this.setCurrentValue(newVal);
},
// 處理 blur 和 focus
handleBlur(event) {
this.$emit('blur', event);
this.$refs.input.setCurrentValue(this.currentValue);
},
handleFocus(event) {
this.$emit('focus', event);
},
// 設置當前value
setCurrentValue(newVal) {
const oldVal = this.currentValue;
if (newVal >= this.max) newVal = this.max;
if (newVal <= this.min) newVal = this.min;
if (oldVal === newVal) {
// 執(zhí)行 el-input 中的 setCurrentValue 方法
this.$refs.input.setCurrentValue(this.currentValue);
return;
}
// 觸發(fā)事件羽莺,改變value
this.$emit('change', newVal, oldVal);
this.$emit('input', newVal);
this.currentValue = newVal;
},
// 處理文本框變化
handleInputChange(value) {
const newVal = value === '' ? undefined : Number(value);
if (!isNaN(newVal) || value === '') {
this.setCurrentValue(newVal);
}
}
},
mounted() {
// 更改 el-input 內(nèi)部 input 的屬性。
let innerInput = this.$refs.input.$refs.input;
innerInput.setAttribute('role', 'spinbutton');
innerInput.setAttribute('aria-valuemax', this.max);
innerInput.setAttribute('aria-valuemin', this.min);
innerInput.setAttribute('aria-valuenow', this.currentValue);
innerInput.setAttribute('aria-disabled', this.inputNumberDisabled);
},
updated() {
// 更改 el-input 內(nèi)部 input 的屬性洞豁。
let innerInput = this.$refs.input.$refs.input;
innerInput.setAttribute('aria-valuenow', this.currentValue);
}
};
</script>
先看HTML盐固,這個組件由兩個span按鈕當做加減按鈕,一個 el-input 組件顯示數(shù)字結(jié)果丈挟。但是刁卜,對于最后兩個 template 的作用不是很明白,不知道何時使用曙咽。
再看JS蛔趴,代碼中導入了 el-input 組件、 Focus 方法和 RepeatClick 方法例朱。這兩個方法之后詳述孝情。看到代碼中還是以props茉继、inject、compute方法來處理傳入的數(shù)據(jù)蚀乔。和前三個組件不同的地方在于烁竭,一是引用了組件需要處理組件,在 mounted 和 updated 方法執(zhí)行時修改 el-input 組件中 input 的屬性吉挣;二是加入了加法和減法的方法邏輯:求精度派撕、做加減法、處理最大最小值限制睬魂。
從CSS角度上來說终吼,要處理加法、減法按鈕的位置和樣式等功能……SCSS方面的內(nèi)容暫且一概不論氯哮。
順便看看 Focus 和 RepeatClick 方法~
// src/directives/repeat-click.js
import { once, on } from 'element-ui/src/utils/dom';
// on 添加監(jiān)聽事件
// once 監(jiān)聽一次事件
export default {
bind(el, binding, vnode) {
let interval = null;
let startTime;
// 執(zhí)行表達式方法
const handler = () => vnode.context[binding.expression].apply();
// 清除interval
const clear = () => {
if (new Date() - startTime < 100) {
handler();
}
clearInterval(interval);
interval = null;
};
// 監(jiān)聽 mousedown 鼠標點擊事件
on(el, 'mousedown', (e) => {
if (e.button !== 0) return;
startTime = new Date();
once(document, 'mouseup', clear);
clearInterval(interval);
// setInterval() 方法可按照指定的周期(以毫秒計)來調(diào)用函數(shù)或計算表達式际跪。
interval = setInterval(handler, 100);
});
}
};
其中導入的on和once方法類似于vue的方法,on用于監(jiān)聽事件喉钢,once監(jiān)聽一次事件姆打。監(jiān)聽 mousedown 事件。如果觸發(fā)每 100 毫秒執(zhí)行一次 handler肠虽,如果監(jiān)聽到 mousedown 判斷時間間隔如果短于100毫秒幔戏,執(zhí)行 handler 方法。取消計時器并清空 interval税课。從而實現(xiàn)了重復點擊的效果闲延。
// src/mixins/focus.js
export default function(ref) {
return {
methods: {
focus() {
this.$refs[ref].focus();
}
}
};
};
很簡單的方法痊剖,就是用于定位一個元素,執(zhí)行它的 focus 方法垒玲。
所以說:src 中除了 locale 目錄用于國際化和 utils 工具目錄外陆馁,其他目錄下都是用于組件的一些配置上的.如 mixins 目錄用于 mixins 項,directives 目錄下的用于 directives 項。
最后
額……由于對于 CSS 比較菜侍匙,所以暫且不誤人子弟述么,待我學成后再補上這部分內(nèi)容。
總結(jié)下幾個組件給我的感覺:其實 UI 框架主要在 UI 二字上语淘。所以其實講 UI 框架重點應該放在 CSS 上栅炒。邏輯部分其實蠻簡單的。畢竟是組件不是整個項目说莫,組件就是要易用性杨箭、可擴展性。
所以說組件的主要邏輯都是進行傳參的處理和計算储狭。
element的組件模式上其實是定義好 [component].vue
組件文件互婿,然后導入到用戶項目 Vue 實例的 components 項中來使用,其實和自定義 component 基本原理差不多辽狈。
下一篇慈参,來看看一些復雜的組件的代碼邏輯實現(xiàn)~~
打個廣告
上海鏈家-鏈家上海研發(fā)中心需求大量前端、后端刮萌、測試驮配,需要內(nèi)推請將簡歷發(fā)送至 dingxiaojie001@ke.com。