在性能方面,对比Vue2.x,性能提升了1.3~2倍左右;打包后的体积也更小了,如果单单写一个HelloWorld进行打包,只有13.5kb;加上所有运行时特性,也不过22.5kb。
  那么作为终端用户的我们,在开发时,和Vue2.x有什么不同呢?Talk is cheap,我们还是来看代码。

Tree-shaking

  Vue3最重要的变化之一就是引入了Tree-Shaking,Tree-Shaking带来的bundle体积更小是显而易见的。在2.x版本中,很多函数都挂载在全局Vue对象上,比如nextTick、nextTick、nextTick、set等函数,因此虽然我们可能用不到,但打包时只要引入了vue这些全局函数仍然会打包进bundle中。
  而在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积;我们在main.js中就能发现这样的变化:

1
2
3
4
5
6
7
//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

const app = createApp(App);
app.use(router).mount("#app");

  创建app实例方式从原来的newVue()变为通过createApp函数进行创建;不过一些核心的功能比如virtualDOM更新算法和响应式系统无论如何都是会被打包的;这样带来的变化就是以前在全局配置的组件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等变为直接挂载在实例上的方法;我们通过创建的实例来调用,带来的好处就是一个应用可以有多个Vue实例,不同实例之间的配置也不会相互影响:   

1
2
3
4
5
const app = createApp(App)
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

因此Vue2.x的以下全局API也需要改为ES6模块化引入:

  • Vue.nextTick
  • Vue.observable不再支持,改为reactive
  • Vue.version
  • Vue.compile (仅全构建)
  • Vue.set (仅兼容构建)
  • Vue.delete (仅兼容构建)

  除此之外,vuex和vue-router也都使用了Tree-Shaking进行了改进,不过api的语法改动不大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//src/store/index.js
import { createStore } from "vuex";

export default createStore({
state: {},
mutations: {},
actions: {},
modules: {},
});
//src/router/index.js
import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});

生命周期函数

  我们都知道,在Vue2.x中有8个生命周期函数:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

  在vue3中,新增了一个setup生命周期函数,setup执行的时机是在beforeCreate生命函数之前执行,因此在这个函数中是不能通过this来获取实例的;同时为了命名的统一,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted,因此vue3有以下生命周期函数:

  • beforeCreate(建议使用setup代替)
  • created(建议使用setup代替)
  • setup
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

  同时,vue3新增了生命周期钩子,我们可以通过在生命周期函数前加on来访问组件的生命周期,我们可以使用以下生命周期钩子:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onErrorCaptured
  • onRenderTracked
  • onRenderTriggered

  那么这些钩子函数如何来进行调用呢?我们在setup中挂载生命周期钩子,当执行到对应的生命周期时,就调用对应的钩子函数:

1
2
3
4
5
6
7
8
9
10
11
12
import { onBeforeMount, onMounted } from "vue";
export default {
setup() {
console.log("----setup----");
onBeforeMount(() => {
// beforeMount代码执行
});
onMounted(() => {
// mounted代码执行
});
},
}

响应式API

我们在深入学习Object.defineProperty和Proxy讲解过Proxy优点以及Vue3为什么改用Proxy实现响应式,同时Vue3也将一些响应式的API进行抽离,以便代码更好的复用。
  我们可以使用reactive来为JS对象创建响应式状态:

1
2
3
4
5
6
import { reactive, toRefs } from "vue";
const user = reactive({
name: 'Vue2',
age: 18,
});
user.name = 'Vue3'

reactive函数只接收object和array等复杂数据类型。

对于一些基本数据类型,比如字符串和数值等,我们想要让它变成响应式,我们当然也可以通过reactive函数创建对象的方式,但是Vue3提供了另一个函数ref:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from "vue";
const num = ref(0);
const str = ref("");
const male = ref(true);

num.value++;
console.log(num.value);

str.value = "new val";
console.log(str.value);

male.value = false;
console.log(male.value);

ref返回的响应式对象是只包含一个名为value参数的RefImpl对象,在js中获取和修改都是通过它的value属性;但是在模板中被渲染时,自动展开内部的值,因此不需要在模板中追加.value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
</div>
</template>

<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>

__END__