[element-ui源码]element-ui有哪些内置的directives?

1,682 阅读6分钟

1.回顾directives基础

(1)钩子函数

一个指令定义对象可以提供如下几个钩子函数:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

(2)钩子函数参数

a例子:<div v-directive:foo="method"></div>
b例子:<div v-directive.foo.bar="1+1"></div>

以上面的两个dom元素为例子:

  • el:指令所绑定的元素,可以用来直接操作DOM。以两个上面例子则是该div元素自身。
  • binding:一个对象,包含以下 property:
  1. name:指令名,不包括 v- 前缀。以上例子都是是 'directive'字符串

  2. value:指令的绑定值。a例子为method变量,b例子为2

  3. oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。

  4. expression:字符串形式的指令表达式。a例子为'method'字符串,b例子为'1+1'字符串

  5. arg:传给指令的参数,a例子为'foo'字符串。

  6. modifiers:一个包含修饰符的对象。b例子为{ foo: true, bar: true }对象

  • vnode:Vue 编译生成的虚拟节点。

  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

更多详情可看Vue Directive

2.element-ui中的directives

(1)mousewheel.js

源码

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);
  }
}

解释

首先先解释引入的normalize-wheel,这个包用于统一监听鼠标滚轮事件。滚轮事件如果考虑到兼容不同的浏览器和浏览器的新旧版本则是一个非常繁琐的过程。在不同类型以及不同版本的浏览器中,我们可以通过以下4种dom事件监听滚轮滚动:

  1. 'wheel':Chrome(61+), FireFox(17+), IE(9+)

  2. 'mousewheel':Chrome, IE(6+), Opera, Safari

  3. 'MozMousePixelScroll':FireFox(3.5 only!)

  4. 'DOMMouseScroll':FireFox(0.9.7+)

更让人头大的是,被使用较多的'mousewheel'事件在回调函数中传入的event参数值也会因浏览器和设备以及操作系统的不同而不同,例如:

  • event.detail:这个值除了Opera浏览器以外其他浏览器一直为0。在Opera,这个值为正代表滚动条移动方向为底部或右边,为负代表方向为顶部或左边。然而在Mac中,该值是根据加速滚动量计算的。在Linux中,该值会一直是2、-2其中一值。
  • event.wheelDeltaX&event.wheelDeltaY:Chrome(window),Chrome(linux),Opera,Safari中这两值各有不同,太多了不是文章重点不想写了,有兴趣可看MDN mousewheel

因此,为了统一监听事件和回调传入参数,这里引用在facebook的fixed-data-table组件中提取出来的'normalize-wheel'组件。

const normalized = normalizeWheel(event);

执行上述代码后返回的normalized的数据结构为:{spinX,spinY,pixelX,pixelY }。可以引用源码中的解释对这些参数进行说明:

spin is trying to normalize how far the wheel was spun (or trackpad dragged). This is super useful for zoom support where you want to throw away the chunky scroll steps on the PC and make those equal to the slow and smooth tiny steps on the Mac. Key data: This code tries to resolve a single slow step on a wheel to 1.

意为spinX(横向)和spinY(纵向)会和mousewheel中Mac的增量滚动一样计算,以一个抽象的距离滚动为单位,滚动事件中,如果纵向位移为一个距离滚动则spinY=1,位移为两个距离滚动则spinY=2。若数值为正则代表向右或向下滚动,为负代表向左或向上滚动。

pixel is normalizing the desired scroll delta in pixel units. You'll get the crazy differences between browsers, but at least it'll be in pixels!

意为pixelX(横向)和pixelY(纵向)会以像素为单位展示位移。若pixelY为100则代表向下位移100px。

Why are there spinX, spinY (or pixels)?

 spinX is a 2-finger side drag on the trackpad, and a shift + wheel turn with a mouse. It results in side-scrolling in the browser by default. 

 spinY is what you expect -- it's the classic axis of a mouse wheel.

这段用于解释为什么分为spinX和spinY,或者pixelX和pixelY。我觉得没什么好解释的,横向滚动事件可以通过笔记本触摸板或者按住shift+滚轮触发。

另外,引入的normalizeWheel还有一个静态方法getEventType(源码看下面),调用后可知道在用户浏览器中用于监听滚动事件的最适合的dom事件是哪个。

/**
 * The best combination if you prefer spinX + spinY normalization.  It favors
 * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
 * 'wheel' event, making spin speed determination impossible.
 */
normalizeWheel.getEventType = function() /*string*/ {
  return (UserAgent_DEPRECATED.firefox())
           ? 'DOMMouseScroll'
           : (isEventSupported('wheel'))
               ? 'wheel'
               : 'mousewheel';
};

module.exports = normalizeWheel;

因此,对于开头element-ui中mousewheel.js的代码,也可以这么修改:

const mousewheel = function(element, callback) {
  if (element && element.addEventListener) {
    // element.addEventListener(isFirefox ? 'DOMMouseScroll' : 'mousewheel', function(event) { 
    // 改成下面这行
    element.addEventListener(normalizeWheel.getEventType(), function(event) {
      const normalized = normalizeWheel(event);
      callback && callback.apply(this, [event, normalized]);
    });
  }
}

(2)repeat-click.js

源码:

import { once, on } from 'element-ui/src/utils/dom';

export default {
  bind(el, binding, vnode) {
    let interval = null;
    let startTime;
    const handler = () => vnode.context[binding.expression].apply();
    const clear = () => {
      if (Date.now() - startTime < 100) {
        handler();
      }
      clearInterval(interval);
      interval = null;
    };

    on(el, 'mousedown', (e) => {
      if (e.button !== 0) return;
      startTime = Date.now();
      once(document, 'mouseup', clear);
      clearInterval(interval);
      interval = setInterval(handler, 100);
    });
  }
}

解释:

这里简单说一下引入的ononce方法的作用:引入的on方法用于绑定监听事件,相当于addEventListeneronce方法用于绑定监听事件后,回调函数只触发一次,相当于addEventListener(type(事件类型),listener(回调函数),{once:true})。element-ui对着两个方法进行外观模式的封装,解决了不同浏览器的兼容性问题。

上面代码的作用就是,绑定该指令的dom元素在按下鼠标后会每隔100ms触发执行指令绑定的回调函数。在松开鼠标后如果和按下鼠标的时刻差不到100ms也会触发回调函数。总的来说,就是长按鼠标时,会隔100ms触发回调函数。短按时,不到100ms也会触发回调函数。

element-ui中用了这个指令的最直观的组件就是el-input-number。效果如下:

(3)clickoutside.js

这个指令是放在src\utils\clickoutside.js里的。

之前写过对应的文章分析过,地址就直接贴这里了:

[element-ui源码]知晓v-clickoutside指令