vue可复用性


title: vue可复用性&组合

date: 2019-10-14 15:08:18

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • Vue.js # 一级分类
  • Vue.js进阶 # 二级分类

混入 mixin

一个混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被”混合”进组件本身的选项.

例子:

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

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

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

选项合并

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行”合并”.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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");
},
conficting: function () {
console.log("from mixin");
},
},
};

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

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

注意:Vue.extend() 也使用同样的策略进行合并。

全局混入

混入也可以进行全局注册,使用时需要小心.一但使用全局混入,他将影响每一个之后创建的 Vue 实例.

使用恰当时还可以用来为自定义选项注入处理逻辑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption;
if (myOption) {
console.log(myOption);
}
},
});

new Vue({
myOption: "hello!",
});
// => "hello!"

请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。

自定义选项合并策略

自定义选项将使用默认策略,即简单地覆盖已有值。如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies 添加一个函数:

1
2
3
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
};

对于多数值为对象的选项,可以使用与 methods 相同的合并策略:

1
2
var strategies = Vue.config.optionMergeStrategies;
strategies.myOption = strategies.methods;

可以在 Vuex 1.x 的混入策略里找到一个更高级的例子:

1
2
3
4
5
6
7
8
9
10
const merge = Vue.config.optionMergeStrategies.computed;
Vue.config.optionMergeStrategies.vuex = function (toVal, fromVal) {
if (!toVal) return fromVal;
if (!fromVal) return toVal;
return {
getters: merge(toVal.getters, fromVal.getters),
state: merge(toVal.state, fromVal.state),
actions: merge(toVal.actions, fromVal.actions),
};
};

自定义指令

输入框聚焦

当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:

1
2
3
4
5
6
7
8
//注册一个全局自定义指令v-focus
Vue.directive("focus", {
//当被绑定的元素插入到DOM中时...
inserted: function (el) {
//聚焦元素
el.focus();
},
});

局部指令注册,组件中接受directive:

1
2
3
4
5
6
7
8
directives: {
focus: {
//指令的定义
inserted: function(el){
el.focus()
}
}
}

然后在模板中任何元素上使用v-focus属性,例如:

1
<input v-focus>

钩子函数

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

  1. bind: 只调用一次,指令第一次绑定到元素时调用.在这里进行一次性的初始化设置.
  2. inserted: 被绑定元素插入到父节点时调用.
  3. update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前.指令的值可能发生改变,也可能没有.
  4. componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用.
  5. unbind: 只调用一次,指令与元素解绑时调用.

钩子函数的参数

指令钩子会被传入以下参数:

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

  • 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 编译生成的虚拟节点.

  • oldVNode: 上一个虚拟节点.

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//html
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>

//js
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})

new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})

动态指令参数

v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!

例如创建一个自定义指令,通过固定布局将元素固定在页面上.

1
2
3
4
<div id="baseexample">
<p>Scroll down this page</p>
<p v-pin="200">Stick me 200px from the top of the page<p>
</div>

自定义事件

1
2
3
4
5
6
7
8
9
10
Vue.directive('pin', {
bind: function(el, bingding, vnode){
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})

new Vue({
el: '#baseexample"
})

这会把元素固定在距离页面顶部 200px 的位置.

如果需要将元素固定在左侧呢?

1
2
3
4
<div id="dynamicexample">
<p>Scroll down this page</p>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.<p>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.directive("pin", {
bind: function (el, binding, vnode) {
el.style.position = "fixed";
var s = binding.arg == "left" ? "left" : "top";
el.style[s] = binding.value + "px";
},
});

new Vue({
el: "#dynamicexample",
data: function () {
return {
direction: "left",
};
},
});

函数简写

在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:

1
2
3
Vue.directive("color-swatch", function (el, binding) {
el.style.backgroundColor = binding.value;
});

对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

1
2
3
4
5
6
7
//html
<div v-demo="{ color: 'white', text: 'hello!' }"></div>;
//js
Vue.directive("demo", function (el, binding) {
console.log(binding.value.color); // => "white"
console.log(binding.value.text); // => "hello!"
});

渲染函数&JSX

渲染函数在 Vue.js 进阶里有讲到,JSX 先不看了.

插件

插件用来为 Vue 添加全局功能.

插件常用功能:

  1. 添加全局方法或属性.
  2. 添加全局资源: 指令,过滤器,过渡
  3. 通过全局混入来添加一些组件选项.
  4. 添加 Vue 实例方法,通过把他们添加到 Vue.prototype 上实现.
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能.如 Vue-router

使用插件

通过全局方法Vue.use()使用插件.需要在new Vue()之前调用.

也可以传入一个可选的对象.

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

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use()。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use()

开发插件

Vue.js 的插件暴露一个 install 的方法.这个方法的第一个参数是 Vue 构造器,第二个参数是可选的选项对象:

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) {
//逻辑...
};
};

过滤器

过滤器可以用在:双花括号插值和 v-bind 表达式

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

或者在创建 Vue 实例之前全局定义过滤器:

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
2
3
4
5
{
{
message | filterA | filterB;
}
}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

过滤器可以接收参数:

1
2
3
4
5
{
{
message | filterA("arg1", arg2);
}
}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 ‘arg1’ 作为第二个参数,表达式 arg2 的值作为第三个参数。