乐趣区

vue指令插件filter

前言

这里开始介绍一些 vue 技巧,用起来提升效率也比较方便!

vue 指令

指令在 vue 代码中很常见,形如 v -model,v-bind,v-on 等,官方还提供了简写方式。

目标在与弄清 v -xx 的含义,以及如何封装自定义指令

1: 为何在代码中可以使用 v -focus 的写法,是由于全局注册了如下指令

// 指令使用
<div v-focus="xxx">baby</div>
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()}
})

// 也可以局部注册,在组件中
// 局部注册时由于是可复用逻辑,一般放在 mixin 中
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {el.focus()
    }
  }
}

2: 看到以上的写法自然需要关注所提供的钩子函数和可选参数

钩子函数,有点类似于组件的生命周期

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

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

可选参数

  • el:指令所绑定的元素,可以用来直接操作 DOM。

    • 虽然 vue 希望数据驱动,在一些场景下还是需要操作 dom, 但是需要保证 dom 的渲染更新,需要配合 nextTick
  • binding:一个对象,包含以下属性:

    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive=”1 + 1″ 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive=”1 + 1″ 中,表达式为 “1 + 1″。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 {foo: true, bar: true}。
  • vnode:Vue 编译生成的虚拟节点,也就是 vue 组件的产物,又叫做 virtual dom
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。一般只在 patch 的过程中涉及二者的对比

3: 可以看到指令中主要还是涉及到 dom 元素的获取以及属性的赋值,不要滥用自定义指令!

4: 一个移动端无限加载指令的例子

  • 下拉一定距离实现加载数据
 // 使用 <div v-load-more="func"></div>
 const loadMore = {
    directives: {
        'load-more': {bind: (el, binding) => {
                let windowHeight = window.screen.height; // 屏幕高度
                let height;
                let setTop;
                let paddingBottom;
                let marginBottom;
                let requestFram;
                let oldScrollTop;
                let scrollEl;
                let heightEl;
                // 获取元素属性
                let scrollType = el.attributes.type && el.attributes.type.value;
                let scrollReduce = 2; // 这个是一个缓冲值,处理手指滑动与实际移动距离
                // 根据 type 确定 scroll 对象
                if (scrollType == 2) {
                    scrollEl = el;
                    heightEl = el.children[0];
                } else {
                    scrollEl = document.body;
                    heightEl = el;
                }
                // 移动端事件处理三段式
                el.addEventListener('touchstart', () => {
                    height = heightEl.clientHeight;
                    if (scrollType == 2) {height = height}
                    // 返回当前元素相对于其 offsetParent 元素的顶部的距离
                    setTop = el.offsetTop;
                    paddingBottom = getStyle(el, 'paddingBottom');
                    marginBottom = getStyle(el, 'marginBottom');
                }, false)

                el.addEventListener('touchmove', () => {loadMore();
                }, false)

                el.addEventListener('touchend', () => {
                    oldScrollTop = scrollEl.scrollTop;
                    moveEnd();}, false)

                const moveEnd = () => {requestFram = requestAnimationFrame(() => {if (scrollEl.scrollTop != oldScrollTop) {
                            oldScrollTop = scrollEl.scrollTop;
                            moveEnd()} else {cancelAnimationFrame(requestFram);
                            height = heightEl.clientHeight;
                            loadMore();}
                    })
                }
        
                const loadMore = () => {
                    // 滚动值逻辑触发 loadmore
                    if (scrollEl.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom - scrollReduce) {
          // 实际绑定在指令的函数调用
                        binding.value();}
                }
            }
        }
    }
};

5: 比较好奇的是 v -model 背后做了什么?

基于以上的理解我们可以来自己一步步实现 v -model

  1. v-model 做了什么,在 <input v-model = “text”> 这种写法下

在代码中修改 text 值,输入框显示改变 model->view, 直接在输入框中修改显示的值,监听 onchange 事件打印 text 值修改 model->view。从数据流的角度考虑 vue 是单向流动,v-model 的实现只是一种语法糖。我们实际还是遵守的 model->view。

//data 到模版的部分 vue 已经做了
directives: {
    'model': {bind: (el, binding) => {
    // 监听 onchange 即可
        el.addEventListener('onChange', () => {
                    this.text = binding.value
          // text 的修改触发视图变化,实际还是数据驱动
                }, false)
    }
  }
}

插件

插件 plugin 的使用比指令的范围更广,而且插件的目的是全局使用,它的 install 方法决定在 vue 实例上挂载相应属性

插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:

  • 1. 添加全局方法或者属性,如: vue-custom-element
  • 2. 添加全局资源:指令 / 过滤器 / 过渡等,如 vue-touch
  • 3. 通过全局 mixin 方法添加一些组件选项,如: vue-router
  • 4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router

我们知道在注册插件的时候一般是 vue.use(xxxplugin)

  • 安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
  • install 其实就是手动提供 vue 实例全局挂载的方式,可以是指令,全局方法,属性等,这个需要看提供的 xxxplugin 是哪一种形式, 别问,问就全局生效!
export function initUse (Vue: GlobalAPI) {
// use 的参数是函数或者对象
  Vue.use = function (plugin: Function | Object) {
  // 检查是否已经注册过
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {return this}

    // additional parameters
    // 提供的参数除了插件以外的数组
    const args = toArray(arguments, 1)
    // 头部是 vue 实例
    args.unshift(this)
    if (typeof plugin.install === 'function') {
    // 执行 obj 本身的 install
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
    // 没有就直接把 plugin 当 Install 执行
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

参考阅读

https://segmentfault.com/a/1190000016256277(use 源码)

https://segmentfault.com/a/1190000012827871(vue 指令介绍)

https://segmentfault.com/a/1190000012823043(filter 参考)

退出移动版