element-ui源碼閱讀-指令

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:指令綁定的前一個值圆雁,僅在 updatecomponentUpdated 鉤子中可用。無論值是否改變都可用帆谍。
    • expression:字符串形式的指令表達(dá)式伪朽。
    • arg:傳給指令的參數(shù),可選汛蝙。
    • modifiers:一個包含修飾符的對象烈涮。
  • vnode:Vue 編譯生成的虛擬節(jié)點。
  • oldVnode:上一個虛擬節(jié)點窖剑,僅在 updatecomponentUpdated 鉤子中可用坚洽。

2.點擊元素邊界外

該指令主要是用于判斷點擊的點是否在綁定元素的范圍內(nèi)。該指令一般用在彈窗中西土,如經(jīng)常用到的Popover組件讶舰,下拉搜索等。具體實現(xiàn)思路如下所示:

    1. 添加v-clickoutside="close"指令
  • 2.為document添加鼠標(biāo)按下彈起的事件需了。
  • 3.綁定元素跳昼,并創(chuàng)建相應(yīng)的鼠標(biāo)事件函數(shù)。
    1. 監(jiān)聽鼠標(biāo)彈起事件肋乍,判斷點擊的點是否在元素外鹅颊。
    1. 執(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)的:

    1. 引入指令文件踪旷,import RepeatClick from 'element-ui/src/directives/repeat-click';
    1. directives中注冊指令曼氛。
    1. 使用指令,v-repeat-click="decrease"
    1. bind事件中定義一個clear的函數(shù)令野,用于清除定時器舀患。
  • 5.為綁定指令的元素添加moursedown事件。
  • 6.在moursedown回調(diào)事件中气破,定義一個定時器聊浅,每100秒執(zhí)行一次回調(diào)函數(shù)。
    1. 鼠標(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)該盡可能多封裝一些組件來提升工作效率竿屹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市灸姊,隨后出現(xiàn)的幾起案子拱燃,更是在濱河造成了極大的恐慌,老刑警劉巖力惯,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碗誉,死亡現(xiàn)場離奇詭異,居然都是意外死亡父晶,警方通過查閱死者的電腦和手機哮缺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甲喝,“玉大人尝苇,你說我怎么就攤上這事。” “怎么了糠溜?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵淳玩,是天一觀的道長。 經(jīng)常有香客問我非竿,道長蜕着,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任红柱,我火速辦了婚禮承匣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锤悄。我一直安慰自己韧骗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布零聚。 她就那樣靜靜地躺著宽闲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪握牧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天娩梨,我揣著相機與錄音沿腰,去河邊找鬼。 笑死狈定,一個胖子當(dāng)著我的面吹牛颂龙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纽什,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼措嵌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芦缰?” 一聲冷哼從身側(cè)響起企巢,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎让蕾,沒想到半個月后浪规,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡探孝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年笋婿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顿颅。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡缸濒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庇配,我是刑警寧澤斩跌,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站讨永,受9級特大地震影響滔驶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卿闹,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一揭糕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锻霎,春花似錦著角、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冰更,卻和暖如春产徊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜀细。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工舟铜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奠衔。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓谆刨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親归斤。 傳聞我的和親對象是個殘疾皇子痊夭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容