在element-ui
源碼中運用了四個指令升熊,分別為點擊元素外,滾輪事件優(yōu)化涩咖,單擊事件優(yōu)化海诲,獲取ref指令。這些指令在平時的開發(fā)中也會經(jīng)常用到檩互,下面就來一一介紹這些指令的實現(xiàn)方式以及用途特幔。
1.什么是指令
在理解element-ui
中相關(guān)的指令前,先來了解下什么是指令盾似,內(nèi)置指令以及怎么創(chuàng)建自定義指令敬辣。
1.1 指令概念
vue
中指令都是以v-
開頭,作用于html
標(biāo)簽零院,提供一些特殊的特性溉跃,當(dāng)指令被綁定到html
元素的時候,指令會為被綁定的元素添加一些特殊的行為告抄,可以將指令看成html
的一種屬性撰茎,用于操作DOM
。
1.2 內(nèi)置指令
vue
中提供了一些內(nèi)置指令打洼,如下所示:
v-text
:更新元素的textContent
龄糊。v-html
:更新元素的innerHTML
逆粹。v-show
:根據(jù)表達(dá)式之真假值,切換元素的display CSS property
炫惩。v-if
:條件渲染僻弹,用于判斷是否顯示元素。在切換時元素及它的數(shù)據(jù)綁定 / 組件被銷毀并重建v-else
:配合v-if
一起使用他嚷。v-else-if
:配合v-if
一起使用蹋绽。v-for
:基于源數(shù)據(jù)多次渲染元素或模板塊。v-on
:綁定事件監(jiān)聽器筋蓖。v-bind
:動態(tài)地綁定一個或多個attribute
卸耘,或一個組件prop
到表達(dá)式。v-model
:在表單控件或者組件上創(chuàng)建雙向綁定粘咖。v-slot
:提供具名插槽或需要接收 prop 的插槽蚣抗。v-pre
:跳過這個元素和它的子元素的編譯過程∥拖拢可以用來顯示原始Mustache
標(biāo)簽翰铡。跳過大量沒有指令的節(jié)點會加快編譯。v-cloak
:這個指令保持在元素上直到關(guān)聯(lián)實例結(jié)束編譯唱捣。v-once
:只渲染元素和組件一次两蟀。隨后的重新渲染,元素/組件及其所有的子節(jié)點將被視為靜態(tài)內(nèi)容并跳過震缭。這可以用于優(yōu)化更新性能偷俭。
1.3 自定義指令
Vue
推崇數(shù)據(jù)驅(qū)動視圖的理念(數(shù)據(jù)交互,狀態(tài)管理)蟋定,但并非所有情況都適合數(shù)據(jù)驅(qū)動( DOM 的操作)。自定義指令就是一種有效的補充和擴(kuò)展,不僅可用于定義任何的 DOM 操作笼裳,并且是可復(fù)用的喜德。
1.3.1 指令定義
使用Vue.directive(id,definition)
可以進(jìn)行指令定義现横。
- id:指令
id
奋构,定義好后,可以直接通過v-{id}
來使用晌该。- definition:對象肥荔,該對象提供了一些鉤子函數(shù)
1.3.2 鉤子函數(shù)
一個指令定義對象可以提供如下幾個鉤子函數(shù):
- bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用朝群。在這里可以進(jìn)行一次性的初始化設(shè)置燕耿。
- inserted:被綁定元素插入父節(jié)點時調(diào)用 。
- update:所在組件的 VNode 更新時調(diào)用姜胖,但是可能發(fā)生在其子 VNode 更新之前誉帅。指令的值可能發(fā)生了改變,也可能沒有。
- componentUpdated:指令所在組件的
VNode
及其子VNode
全部更新后調(diào)用蚜锨。- unbind:只調(diào)用一次档插,指令與元素解綁時調(diào)用。
1.3.3 鉤子函數(shù)參數(shù)
指令鉤子函數(shù)會被傳入以下參數(shù):
-
el
:指令所綁定的元素亚再,可以用來直接操作 DOM郭膛。 -
binding
:一個對象,包含以下 property:-
name
:指令名氛悬,不包括v-
前綴饲鄙。 -
value
:指令的綁定值。 -
oldValue
:指令綁定的前一個值圆雁,僅在update
和componentUpdated
鉤子中可用。無論值是否改變都可用帆谍。 -
expression
:字符串形式的指令表達(dá)式伪朽。 -
arg
:傳給指令的參數(shù),可選汛蝙。 -
modifiers
:一個包含修飾符的對象烈涮。
-
-
vnode
:Vue 編譯生成的虛擬節(jié)點。 -
oldVnode
:上一個虛擬節(jié)點窖剑,僅在update
和componentUpdated
鉤子中可用坚洽。
2.點擊元素邊界外
該指令主要是用于判斷點擊的點是否在綁定元素的范圍內(nèi)。該指令一般用在彈窗中西土,如經(jīng)常用到的Popover
組件讶舰,下拉搜索
等。具體實現(xiàn)思路如下所示:
- 添加
v-clickoutside="close"
指令- 2.為
document
添加鼠標(biāo)按下和彈起的事件需了。- 3.綁定元素跳昼,并創(chuàng)建相應(yīng)的鼠標(biāo)事件函數(shù)。
- 監(jiān)聽鼠標(biāo)彈起事件肋乍,判斷點擊的點是否在元素外鹅颊。
- 執(zhí)行
v-clickoutside
綁定的close
事件。
import Vue from 'vue';
import { on } from 'element-ui/src/utils/dom';
const nodeList = [];
const ctx = '@@clickoutsideContext';
let startClick;
let seed = 0;
// 添加鼠標(biāo)按下的事件墓造,并緩存event
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));
// 添加鼠標(biāo)點擊后彈起的事件堪伍,遍歷nodeList,執(zhí)行nodeList中元素添加的事件
!Vue.prototype.$isServer && on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});
// 創(chuàng)建元素的點擊事件
function createDocumentHandler(el, binding, vnode) {
// 以彈起和彈出作參數(shù)
return function(mouseup = {}, mousedown = {}) {
// 先判斷點擊的對象是否為指令綁定的元素本身或空元素對象
if (!vnode ||
!vnode.context ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.context.popperElm &&
(vnode.context.popperElm.contains(mouseup.target) ||
vnode.context.popperElm.contains(mousedown.target)))) return;
// 獲取指令的表達(dá)式
if (binding.expression &&
el[ctx].methodName &&
// 執(zhí)行指令綁定的方法
vnode.context[el[ctx].methodName]) {
vnode.context[el[ctx].methodName]();
} else {
el[ctx].bindingFn && el[ctx].bindingFn();
}
};
}
/**
* v-clickoutside
* @desc 點擊元素外面才會觸發(fā)的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose">
* ```
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el);//將綁定的元素對象添加到數(shù)組中
const id = seed++;
// 給綁定的元素對象添加點擊觸發(fā)的方法觅闽,使用一個變量存儲
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
};
},
// 更新
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
el[ctx].methodName = binding.expression;
el[ctx].bindingFn = binding.value;
},
// 解除綁定
unbind(el) {
let len = nodeList.length;
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
delete el[ctx];
}
};
3.單擊事件優(yōu)化
在src/directives
目錄下有一個repeat-click.js
文件帝雇,該文件就是一個用于優(yōu)化單擊事件的指令,我們平時點擊時谱煤,正常的點擊邏輯是這樣的:當(dāng)用戶按住鼠標(biāo)左鍵時摊求,會觸發(fā)mousedown
的回調(diào)。但當(dāng)一直按住鼠標(biāo)左鍵不松手時,就不會觸發(fā)mousedown
的回調(diào)室叉,使用該指令就是為了實現(xiàn)一直按住鼠標(biāo)左鍵不松手時睹栖,也能執(zhí)行對應(yīng)的事件,這指令主要用在InputNumber
組件中茧痕,當(dāng)鼠標(biāo)點擊-
或+
不松開時野来,數(shù)字可以持續(xù)的進(jìn)行加減。下面就來看看指令是怎么實現(xiàn)的:
- 引入指令文件踪旷,
import RepeatClick from 'element-ui/src/directives/repeat-click';
- 在
directives
中注冊指令曼氛。
- 使用指令,
v-repeat-click="decrease"
- 在
bind
事件中定義一個clear
的函數(shù)令野,用于清除定時器舀患。- 5.為綁定指令的元素添加
moursedown
事件。- 6.在
moursedown
回調(diào)事件中气破,定義一個定時器聊浅,每100秒執(zhí)行一次回調(diào)函數(shù)。
- 鼠標(biāo)彈起時现使,執(zhí)行
clear
函數(shù)清除定時器低匙。
import { once, on } from '@/utils/dom';
export default {
bind(el, binding, vnode) {
let interval = null;
let startTime;
// 獲取指令綁定的事件函數(shù)
const handler = () => vnode.context[binding.expression].apply();
// 定義一個清除定時器的函數(shù)
const clear = () => {
// 間隔時間小于100毫秒時,繼續(xù)執(zhí)行回調(diào)函數(shù)
if (Date.now() - startTime < 100) {
handler();
}
clearInterval(interval);
interval = null;
};
// 添加點擊事件
on(el, 'mousedown', (e) => {
if (e.button !== 0) return;
// 緩存點擊時的時間
startTime = Date.now();
// 添加鼠標(biāo)彈起的事件
once(document, 'mouseup', clear);
// 清除定時器
clearInterval(interval);
// 100毫秒執(zhí)行一次回調(diào)函數(shù)
interval = setInterval(handler, 100);
});
}
};
4.滾輪事件優(yōu)化
在src/directives
目錄下有一個mousewheel.js
文件碳锈,該指令主要是對鼠標(biāo)滾動事件進(jìn)行了優(yōu)化顽冶,使用normalize-wheel
這個庫來解決不同瀏覽器之間的兼容性來獲取x
方向和y
方向的滾動偏移量。
import normalizeWheel from 'normalize-wheel';
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const mousewheel = function(element, callback) {
if (element && element.addEventListener) {
element.addEventListener(isFirefox ? 'DOMMouseScroll' : 'mousewheel', function(event) {
const normalized = normalizeWheel(event);
callback && callback.apply(this, [event, normalized]);
});
}
};
export default {
bind(el, binding) {
mousewheel(el, binding.value);
}
};
5.獲取ref指令
在packages/popover/src/
目錄下有一個directive.js
文件售碳,該指令主要是用于Popover
組件强重,用于獲取Popover
組件的ref
,
const getReference = (el, binding, vnode) => {
const _ref = binding.expression ? binding.value : binding.arg;
const popper = vnode.context.$refs[_ref];
if (popper) {
if (Array.isArray(popper)) {
popper[0].$refs.reference = el;
} else {
popper.$refs.reference = el;
}
}
};
export default {
bind(el, binding, vnode) {
getReference(el, binding, vnode);
},
inserted(el, binding, vnode) {
getReference(el, binding, vnode);
}
};
總結(jié)
element-ui
的指令基本上都介紹完了,平時開發(fā)的時候除了使用vue
內(nèi)置的指令外贸人,應(yīng)該盡可能多封裝一些組件來提升工作效率竿屹。