Vue指南的要点笔记(十一)

本篇开始学习Vue可复用性编程的内容,主要要点:

  • mixin
  • 插件
  • 过滤器

mixin

混入是一种常见的代码复用方式,前端成为mixin,php里面叫做traits。在vue里面,混入是一个普通的javascript对象:

1
2
3
4
5
6
7
8
9
10
11
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}

这个普通的对象,可以使用Vue的选项对象options定义的所有属性,参见vue的官方文档选项相关的内容注意data属性在mixin对象里面,也要定义为一个函数
Vue的选项对象里面,有一个特殊的属性mixins,用来给Vue组件输入mixin对象,以达到复用mixin对象功能的目的。这个mixins属性是一个数组属性,所以可以接收多个mixin对象,所有mixin对象的功能最终在构造vm实例的时候,都会被复制给vm。

1
2
3
4
5
6
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

由于mixin对象和Vue的选项对象,完全使用相同的属性名称,且Vue选项对象的mixins属性是一个数组,所以很容易在mixin对象之间、以及mixin对象与Vue选项对象之间存在同名属性的冲突,Vue按照如下规则来处理冲突:

  • 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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" }
    }
    })
  • 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var mixin = {
    created: function () {
    console.log('混入对象的钩子被调用')
    }
    }

    new Vue({
    mixins: [mixin],
    created: function () {
    console.log('组件钩子被调用')
    }
    })

    // => "混入对象的钩子被调用"
    // => "组件钩子被调用"
  • 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var mixin = {
    methods: {
    foo: function () {
    console.log('foo')
    },
    conflicting: function () {
    console.log('from mixin')
    }
    }
    }

    var vm = new Vue({
    mixins: [mixin],
    methods: {
    bar: function () {
    console.log('bar')
    },
    conflicting: function () {
    console.log('from self')
    }
    }
    })

    vm.foo() // => "foo"
    vm.bar() // => "bar"
    vm.conflicting() // => "from self"

以上选项合并策略,除了发生在mixin对象与vue选项对象之间,还可能发生在Vue.extend所创建的Vue子类之上。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let FadeComp = Vue.extend({
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
template: `<transition name="fade"><slot></slot></transition>`
});

new FadeComp({
data: function () {
return {
message: 'hello',
foo: 'abc'
}
},
created(){
console.log(this.$data);
// => { message: "goodbye", foo: "abc", bar: "def" }
}
});

通过这个例子可以看到Vue.extend定义一个Vue子类时传入的options对象,可以看作是一个mixin对象,当对这个子类进行实例化的时候,传入的options对象,才是组件的选项对象,优先级更高,且会自动采用类似mixin对象的合并策略。

全局mixin

Vue提供了一个静态方法:Vue.mixin,可以定义全局的mixin对象,不需要通过mixins属性注入,所有的Vue实例都会应用。这个api威力强大,但是也很危险,需慎用。

自定义选项的合并策略

不管是mixin对象,还是Vue.extend使用的options对象,最终都会组件的选项对象发生合并,Vue默认的合并策略,可能不满足项目的需求,所以Vue提供了自定义选项的合并策略,选项对象的任意属性都能自定义它的合并策略,只需要在Vue.config.optionMergeStrategies这个配置对象上,为选项对象的属性定义一个函数:

1
2
3
Vue.config.optionMergeStrategies.myOption = function (parent, child, vm) {
// 返回合并后的值
}

合并策略选项分别接收在父实例和子实例上定义的该选项的值作为第一个和第二个参数,Vue实例上下文被作为第三个参数传入。

1
2
3
4
5
6
7
8
9
Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) {
return child + 1
}

const Profile = Vue.extend({
_my_option: 1
})

// Profile.options._my_option = 2

插件

插件是扩展Vue的一种方式,定义一个插件非常简单,只需要提供一个包含install方法的对象即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}

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

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

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

install方法接收个参数,第一个参数是Vue类,第二个参数是选项对象,当插件被安装到Vue的时候,可以传递一个选项对象,这个选项对象会被install方法的第二个参数接收。

插件如何安装到Vue上:

1
2
3
4
5
6
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
// ...组件选项
})

Vue.use这个api专门用来安装插件,它可以在第二个参数传递一个选项对象:

1
Vue.use(MyPlugin, { someOption: true })

这个选项对象最终会被传递到插件的install方法。插件安装,也就是Vue.use的api应该要在应用程序实例被创建前(new Vue),都调用完。

插件是什么?插件其实什么都不是,它就是把我们裸露在全局环境的对Vue进行自定义的代码,写到一个块中而已。

过滤器

过滤器是一个函数,主要用于文本格式化,可用于模板语法的花括号差值以及v-bind指令中。文本格式化,在产品中其实是非常普遍的需求,比如用户的性别,在数据库中可能存储为0、1,但是在前端可能要展示为男女,此时通过定义全局过滤器,就可以在任何页面中,自动将性别数据转换为普通文本。

过滤器函数,需要放置在js表达式的结尾,并使用管道符号:

1
2
3
4
5
<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

过滤器可以定义在组件的选项中:

1
2
3
4
5
6
7
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}

也可以全局定义:

1
2
3
4
5
6
7
8
9
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
// ...
})

当全局过滤器和局部过滤器重名时,会采用局部过滤器。
过滤器可以串联:

1
{{ message | filterA | filterB }}

过滤器函数总是接收管道符号前面表达式的运行结果,作为第一个参数进行调用;自身的返回值,又会作为第一个参数传入下一个过滤器函数继续调用。
过滤器是 JavaScript 函数,因此可以接收参数:

1
{{ message | filterA('arg1', arg2) }}

这个时候,filter这个过滤器需要定义为三个参数的函数,第一个参数始终是管道符号前面表达式的返回值,第二个和第三个参数,是在过滤器使用的时候传入的,也就是上面的'arg1' arg2