在 RC13 之后,Vue 推出了 3.0 正式版。之前我们尝试了使用新的组装式 API 实现响应式数据,并且和 Vue2 进行了简单对比。

今天继续看看其它日常使用方式的变化与对比吧。

(二)事件处理与 mixin 复用

1. 简单例子

Vue2 中,模板使用到的事件处理函数,通常都被放在 vm 构造参数的 methods 属性中,然后才能通过 v-on:<event>/@<event> 标记到对应 DOM 上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<p>Count: {{count}}</p>
<button @click="increase">Increase</button>
</div>
</template>

<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
increase() {
this.count += 1;
},
},
}
</script>

上面的 increase() 方法中,this 看似指向 methods 属性的对象,实际上和之前 data 返回对象一样,指向的其实是最终创建的 vm 对象,日常指代混乱。

  • data 中返回了数据模板,告知 vm 会有一个名为 count 的响应式数据;
  • methods 对象作为方法模板,告知 vm 需要创建一个名为 increase 的方法,供模板事件处理;
  • 两者看似属于不同的对象,甚至在 export default 对象中不处在同一层级,实际上 this 都指向了 vm 对象。

2. 使用组装式 API 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Vue 3.0
import { ref } from 'vue';

export default {
setup() {
const count = ref(0);
const increase = () => {
count.value += 1;
};
return {
count,
increase,
};
},
};

在 Vue3 的 setup 中,对数据的改动,直接使用普通函数或箭头表达式对数据进行操作就行了,非常直观。

  • 事件处理函数和数据一起通过 return 返回给模板使用;
  • 数据与事件处理逻辑处于同一层级,可以编译前发现命名冲突等问题;
  • 而且便于收拢事件处理函数和相关数据的位置,提高代码可读性和可重构性。

3. 不再需要 mixin

(1) Vue2 的 mixin 实现

对于不同组件可复用的数据和事件处理函数关系,在 Vue2 中我们通常都是用 mixin 来完成的。

比如,不同页面都经常使用到一个 ajax 的网络请求方法,和一个请求状态数据 isRequestSending(可用于在模板内判断和调整界面展示和按键交互),过去的 Vue2 中通常这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// mixin-net.js
export default {
data() {
return {
isRequestSending: false,
};
},
methods: {
async ajax(/* some params... */) {
if (this.isRequestSending) {
return { status: -1, msg: 'network busy' };
}
try {
this.isRequestSending = true;
return await fetch(/* some params... */);
} catch (ex) {
throw ex;
} finally {
this.isRequestSending = false;
}
}
},
};
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
<!-- page-a.vue -->
<template>
<div>
<button @click="callAjax">call ajax</button>
<p>is request sending: {{isRequestSending}}</p>
<p>resonse: {{response}}</p>
</div>
</template>

<script>
import mixinNet from './mixin-net.js';

export default {
mixins: [ mixinNet ],
data() {
return {
response: '',
};
},
methods: {
async callAjax() {
this.response = await this.ajax(/* some params... */);
},
},
};
</script>

可以看出,因为之前 vm 构造参数导致 this 指代混乱的问题,Vue2 中组件的可复用逻辑只好使用 mixin 的方式,将一个与构造参数结构一致的对象混合到一起来实现。

以至于这个 mixin 的结构,同样继承了组件构造参数的毛病。

而且引入 mixin 之前,无法通过标准 es 模块结构分析可用的数据、方法和钩子函数。必须解读参数中字段,甚至函数返回值,才能得知复用逻辑的大致结构。

(2) Vue3 的方案

Vue3 中,你可以使用类似构造函数的结构,在组件中取到返回值后,直接解构使用:

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
// net.js
import { ref } from 'vue';

export default {
setup() {
const isRequestSending = ref(false);
const ajax = async () => {
if (isRequestSending.value) {
return { status: -1, msg: 'network busy' };
}
try {
isRequestSending.value = true;
return await fetch(/* some params... */);
} catch (ex) {
throw ex;
} finally {
isRequestSending.value = false;
}
};
return {
isRequestSending,
ajax,
};
},
};
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
28
29
<!-- page-a.vue -->
<template>
<div>
<button @click="callAjax">call ajax</button>
<p>is request sending: {{isRequestSending}}</p>
<p>resonse: {{response}}</p>
</div>
</template>
<script>
import net from './net.js';

export default {
setup() {
const netMixin = net.setup();
const { ajax } = netMixin;

const response = ref('');
const callAjax = async () => {
response.value = await ajax(/* some params... */);
};
return {
...netMixin,

response,
callAjax,
};
},
};
</script>

也可以根据个人喜好和业务实际情况,考虑做进一步拆分,以便简化代码结构或者实现某些属性的单例控制等效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// net.js
import { ref } from 'vue';

export const isRequestSending = ref(false);

export const ajax = async () => {
if (isRequestSending.value) {
return { status: -1, msg: 'network busy' };
}
try {
isRequestSending.value = true;
return await fetch(/* some params... */);
} catch (ex) {
throw ex;
} finally {
isRequestSending.value = false;
}
};

相比 Vue2 的 mixin,更加自由可控、清晰明了。