vue部分高级特性

概述

文章将讲述指令、混入、高阶组件、函数式组件、@hook、异步组件等内容。若是文中有不当的地方欢迎指正哦!html

特性以及部分原理

自定义指令(directive)

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也容许注册自定义指令。有时候咱们想对dom进行操做的时候,就可使用自定义指令,好比设置标题样式而且让标题一直固定在页面上方,可使用全局注册或者局部注册。而后你能够在模板中任何元素上使用新的 v-title property。vue

//全局注册
<div id="app">
    <div v-title>hello world</div>
</div>
<script>
    Vue.directive('title', {
        inserted: function (el) {
            console.log(el)
            el.style.position = 'fixed' 
            el.style.top = '50px' 
            el.style.left = '48%' 
            el.style.color = '#409EFF' 
        }
    })

    new Vue({
        el: '#app',
        data: {
            message: 'hello!'
        }
    })
</script>
<style>
   #app{
       height: 1000px
   } 
</style>
//局部注册
  new Vue({
    el: '#app',
    directives: {
        title: {
            inserted: function (el) {
                console.log(el)
                el.style.position = 'fixed'
                el.style.top = '50px'
                el.style.left = '48%'
                el.style.color = '#409EFF'
            }
        }
    }
  })
复制代码

image.png

directive钩子函数参数

指令钩子函数会被传入如下参数:node

  • el:指令所绑定的元素,能够用来直接操做 DOM。
  • binding:一个对象,包含如下 property:
  1. name:指令名,不包括 v- 前缀。
  2. value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
  3. oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。不管值是否改变均可用。
  4. expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
  5. arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
  6. modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

咱们打印下函数传入的参数,其实简单来讲就是el就是绑定dom元素,binging指令:后所携带的具体内容,VNode就当还未生成的节点好了。webpack

<div v-title:arr="message">hello world</div>
Vue.directive('title', {
        inserted: function (el, binding, vnode) {
            console.log(el, binding, vnode)
            el.style.position = 'fixed' 
            el.style.top = '50px' 
            el.style.left = '48%' 
            el.style.color = '#409EFF' 
        }
    })
复制代码

image.png

钩子函数

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

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

咱们能够测试下钩子函数的调用时机:web

<div id="app">
    <div id="txt" v-title:data="sum">value: {{sum}}</div>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            sum: 0
        },
        directives: {
            title: {
                bind: (el, bind) => { console.log(bind.value, 'a') },// 第一次绑定元素时调用
                inserted: (el, bind) => { console.log(bind.value, 'b') },// 当被绑定的元素插入到 DOM 中时……
                update: (el, bind) => { console.log(bind.value, 'c') },// 所在组件VNode发生更新时调用
                componentUpdated: (el, bind) => { console.log(bind.value, 'd') }, // 指令所在组件的 VNode 及其子 VNode 所有更新后调用
                unbind: (el, bind) => { console.log(bind.value, 'e') }    // 只调用一次,指令与元素解绑时调用
            }
        },
        mounted() {
            console.log(this.sum, '???')
            let timer = setInterval(() => {
                this.sum++
            }, 200)
            setTimeout(() => {
                clearInterval(timer)
            }, 3000)
        }
    })
</script>
复制代码

image.png

指令大体原理

在页面渲染的过程当中,分别有建立(create)、激活(avtivate)、更新(update)、移除(remove)、销毁(destroy),在这些过程当中,框架在每一个时段都会调用相应的钩子函数,这些hooks中一部分的函数就包含了咱们的指令。源码部分我了解的很少,给你们推荐一篇vue指令原理相关博文www.cnblogs.com/gerry2019/p…vue-router

混入

官方是这样定义的:混入 (mixin) 提供了一种很是灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象能够包含任意组件选项。当组件使用混入对象时,全部混入对象的选项将被“混合”进入该组件自己的选项。其实就是vue实例的一个复用。实用场景:公共组件或者功能,例如获取用户白名单、菜单返回、公共基础table。 值得注意的点:vue-cli

  1. 当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。好比,数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先。
  2. 同名钩子函数将混合为一个数组,所以都将被调用。另外,混入对象的钩子将在组件自身钩子以前调用。
  3. 值为对象的选项,例如 methods, components 和 directives,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})
复制代码

高阶组件

一个函数接受一个组件为参数,返回一个包装后的组件。其实在vue中,组件能够当作一个函数,那从本质上来讲,高阶组件就是高阶函数(JavaScript的函数其实都指向某个变量。既然变量能够指向函数,函数的参数能接收变量,那么一个函数就能够接收另外一个函数做为参数,这种函数就称之为高阶函数)express

高阶函数

举例一个最简单的高阶函数计算次方api

function pow(x, y, f){
    return f(x, y);
  }
  pow(3, 3, Math.pow)
复制代码

在es6中也有不少高阶函数,如map、reduce、filter。

高阶组件的例子

<div id="app">
    <hoc></hoc>
</div>
<script>
    const view = {
        template: `<span>
                    <span>test hoc ...</span>
                    </span>`,
        props: ["result", "loading"],
    };
    const test = (wrapped, txt = 'hello') => {
        return {
            render(h) {
                const args = {
                    props: {
                        result: this.result,
                        loading: this.loading,
                    },
                };
                const wrapper = h("div", [
                    h(wrapped, args),
                    'loading'
                ]);
                return wrapper
            }
        }
    }
    const hoc = test(view, 'hui')
    console.log(hoc);

    new Vue({
        el: '#app',
        components: {
            hoc
        },
        data: {
            sum: 0
        }
    })
</script>
复制代码

image.png

值得注意的点

  1. 高阶组件(HOC)应该是无反作用的纯函数,且不该该修改原组件,就是组件是一个新的组件,不会对原组件作修改。
  2. 高阶组件(HOC)不关心你传递的数据(props)是什么,而且被包装组件(WrappedComponent)不关心数据来源
  3. 高阶组件(HOC)接收到的 props 应该透传给被包装组件(WrappedComponent)
  4. 在高阶组件中渲染函数向子组件中传递做用域插槽时候要注意上下文

动态组件 异步组件 递归组件

动态组件

能够在同组件之间进行动态切换, 动态切换能够经过 Vue 的 元素加一个特殊的 is attribute 来实现:

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<!DOCTYPE html>
<html>
  <head>
    <title>Dynamic Components Example</title>
    <script src="https://unpkg.com/vue"></script>
    <style>
      .tab-button {
        padding: 6px 10px;
        border-top-left-radius: 3px;
        border-top-right-radius: 3px;
        border: 1px solid #ccc;
        cursor: pointer;
        background: #f0f0f0;
        margin-bottom: -1px;
        margin-right: -1px;
      }
      .tab-button:hover {
        background: #e0e0e0;
      }
      .tab-button.active {
        background: #e0e0e0;
      }
      .tab {
        border: 1px solid #ccc;
        padding: 10px;
      }
    </style>
  </head>
  <body>
    <div id="dynamic-component-demo" class="demo">
      <button
        v-for="tab in tabs"
        v-bind:key="tab"
        v-bind:class="['tab-button', { active: currentTab === tab }]"
        v-on:click="currentTab = tab"
      >
        {{ tab }}
      </button>

      <component v-bind:is="currentTabComponent" class="tab"></component>
    </div>

    <script>
      Vue.component("tab-home", {
        template: "<div>Home component</div>"
      });
      Vue.component("tab-posts", {
        template: "<div>Posts component</div>"
      });
      Vue.component("tab-archive", {
        template: "<div>Archive component</div>"
      });

      new Vue({
        el: "#dynamic-component-demo",
        data: {
          currentTab: "Home",
          tabs: ["Home", "Posts", "Archive"]
        },
        computed: {
          currentTabComponent: function() {
            return "tab-" + this.currentTab.toLowerCase();
          }
        }
      });
    </script>
  </body>
</html>
复制代码

异步组件

在大型应用中,咱们可能须要将应用分割成小一些的代码块,而且只在须要的时候才从服务器加载一个模块。为了简化,Vue 容许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件须要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供将来重渲染。

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})
复制代码

在vue-cli中在使用异步组件

const first =()=>import(/* webpackChunkName: "group-foo" */ "../components/first.vue");
复制代码

vue中部分钩子函数(@hook)

Vue 实例同时在其事件接口中提供了其它的方法。咱们能够:

经过 $on(eventName, eventHandler) 侦听一个事件

经过 $once(eventName, eventHandler) 一次性侦听一个事件

经过 $off(eventName, eventHandler) 中止侦听一个事件

你一般不会用到这些,可是当你须要在一个组件实例上手动侦听事件时,它们是派得上用场的。它们也能够用于代码组织工具。例如,你可能常常看到这种集成一个第三方库的模式。官网提供一个案例:在不使用beforeDestroy钩子清picker

//案例一
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}
//案例二
//在父组件在子组件渲染阶段作一些操做
<child
  @hook:mounted="handle"
  @hook:beforeUpdated="xxx"
  @hook:updated="xxx"
/>
method () {
  handle() {
  // do something...
  }
},
复制代码

在vue生命周期中周期都有对应的钩子函数

插件

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

  • 添加全局方法或者 property。如:vue-custom-element

  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch

  • 经过全局混入来添加一些组件选项。如 vue-router

  • 添加 Vue 实例方法,经过把它们添加到 Vue.prototype 上实现。

  • 一个库,提供本身的 API,同时提供上面提到的一个或多个功能。如 vue-router

自定义插件

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}
复制代码

后续

文章都在在官网文档上的整理和概括,只是停留在表层,欢迎你们指正。定下 下一个目标:写一个小型vue-router。

引用

vue官网 cn.vuejs.org/v2/api
探索Vue高阶组件 www.jianshu.com/p/6b149189e…
Vue 进阶必学之高阶组件 HOC zhuanlan.zhihu.com/p/126552443
Vue指令实现原理 www.cnblogs.com/gerry2019/p…

相关文章
相关标签/搜索