Composition API

Limitations of Vue2

  • Readability as components grow, large components can become hard to read & maintain
  • Code reuse patterns have drawbacks
  • Limited TypeScript support

setup

Writing Composition Functions

Vue2 对于代码的跨组件复用有三种方法,但是各有优缺点:


Vue3 增加了一种跨组件复用代码的方法即 Composition API

Composition API

setup() executes before:

  • Components
  • Props
  • Data
  • Methods
  • Computed Propeties
  • Lifecycle methods

setup() doesn’t have access to “this

setup() has two optional arguments:

  1. the first is props, which is reactive and can be watched.
1
2
3
4
5
6
7
8
9
10
11
import { watch } from 'vue';
export default {
props: {
name: String
},
setup(props) {
watch(() => {
console.log(props.name); // 每当 props.name 的值发生改变时,就在控制台打印其值
})
}
}
  1. the optional second argument for setup is context, we use context to access properties that we previously accessed with this.
1
2
3
4
5
6
7
8
9
10
11
12
13
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}

context 是一个普通的 JavaScript 对象,不是响应式的,可以安全地对 context 使用 ES6 解构。

ref() creates a reactive reference: this wraps our primitive in an object, allowing us to track changes(Previously data() was wrapping our primitives in an object.).

Note: With the Compositon API we can declare reactive objects that aren’t assciated with a component.

Reactive reference inside of setup:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>Capacity: {{ capacity }}</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const capacity = ref(3);
return { capacity }; // Returns the variables and functions that our template will need.
}
}
</script>

好处
We can control what gets exposed
We can trace where a property is defined

这种 Composition API syntax 通过插件可以在 Vue2 中使用:
import { ref } from "@vue/composition-api";

How to add methods?

In our regular component syntax:

1
2
3
4
5
methods: {
increaseCapacity() {
this.capacity++;
}
}

新语法添加方法如图所示,在 DOM 中绑定一个点击事件 increaseCapacity,然后在 setup() 中定义一个普通函数 increaseCapacity 并 return,这样就可以在 template 中使用了

由于 setup 中没有 this,那怎么在新语法中实现 capacity++ 呢?不能像下面这样,因为 capacity 是一个对象,一个响应式引用,不能自增(算术操作数必须为类型 “any”、”number”、”bigint” 或枚举类型。)

1
2
3
increaseCapacity() {
capacity++;
}

这时需要使用 capacity.value++,This is How we access the value on a reactive reference.

Why don’t we need to call [capacity].value in the template?
Because when Vue finds a ref in the template it automatically exposes the inner value.

方法中调用并处理 ref:

如何添加计算属性?

在上面代码中加入一个 attending 表示出席的人,前面的 capacity 代表容量,用容量减去出席的人计算一个剩余空间。

Vue2 中的方法是直接在 computed 中定义一个变量计算,如图:

Inside of setup method in Vue3:

  • first, import the computed function;
  • Then create a new constant called spacesLeft(Customized name) which sends into the computed function an anonymous function which returns the result of taking capacity and subtracting the number of people who are attending;
  • Last give the template access to this computed property.

Alternative Reactive Syntax

可能不想一个个声明响应式变量,这时可以用 reactive() 创建一个对象,其它的每个属性都是响应式的,计算属性也可以在其中,注意不需要使用 .value 来访问响应式变量的值了,然后将整个对象 return 给 template

所以响应式变量都在 Event 对象中,template 中引用也要相应改变

新的疑问,总不能每次都是 event. event. event. 这样在 template 中访问其每个属性吧,不然还不如不用 reactive,如何简化模板呢?下图去掉 event. 后就报错了

使用 return { ...event }return { event.capacity, event attending, ... } 也都没效果,原因是拆分 event 对象会取消其响应式功能,The solution here is to take reactive object and split each object up into its own reactive references,Vue3 中提供了 toRefs 函数来解构 reactive 函数创建的对象

Note: 只返回 event 对象时可以简化代码为 return toRefs(event);

Remembering: Why the Composition API?

除了改进型 TypeScript 支持,使用 Composition API 还有两个原因:

  • Component organized by feature;
  • Code reuseable across other components.

不过现在前面的示例都没有表现出 Composition API 的优点,所以需要对前面的代码进行重构,使代码更模块化。

目前的代码,用的 ref,代码少没必要用 reactive:

  1. 要按功能组织代码,就先把代码提到 setup 外面,放在一个 composition function 中,再在 setup() 中调用,这样当有其它功能时再定义另一个 composition function 然后在 setup() 中调用就好了

  2. 实现跨组件复用代码,则移动 composition function 到另一个文件中并导出,然后在要使用的组件中导入

多个 composition function 时,在 setup 中像下图这样返回是错的,不是有效的 JavaScript,不能创建这样的对象

正确的 return 方式,需要解构对象,给模板返回它需要访问并且渲染到页面的所有数据

虽然上面的方法实现了代码复用,不过也有问题,composition function 都封装在文件中,光看文件名不知道哪些对象被哪个 composition function 注入组件中的,所以单个创建本地对象来接收某个 composition function return 的对象。

现在就明确了哪个对象来自哪个文件(Explicitly show objects being received from composition functions)。

以上就是 Vue3 的最佳实践。

生命周期函数

Vue 生命周期钩子:

Vue2 新版本添加的生命周期钩子:

Vue3 的变化:

在组件中使用生命钩子前先导入,然后在 setup() 内给生命钩子函数名前加 “on“ 来使用钩子,下图包含所有可以在 setup() 中使用的钩子函数。

在 Vue3 中不再有 beforeCreat、created 两个生命钩子,因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

Vue3 新增生命钩子

watch

假设有下面这个案例,一个搜索框双向绑定 searchInput,每当输入内容改变 searchInput 的值时就调用 event.js 文件中的 getEventCount 函数处理其值,结果返回给 results。如下代码在页面中打开,在搜索框中不断输入值,但是 results 的值始终没变,这是因为 event.js 中的 getEventCount 函数只在 setup() 运行时调用了一次,后续 searchInput 改变一次也没被调用过。

这里就要用到 watchEffect 语法了,Vue3 的新功能,先导入它,并将代码封装在一个回调函数中传给它,放在 setup 里,回调会在 watchEffect 函数追踪的响应式依赖发生改变时重新执行

可以认为 watchEffect 是 Vue2 侦听器的一个简单版本,Watch 侦听器可以监听改变后的新值与旧值

通过数组传入,可以侦听多个响应式对象,如果回调中需要也可以获取它们各自的新旧值

那把前面的例子换成是 Watch 侦听器代码是怎样的呢?首先还是导入,然后在 watch 函数中传入 searchInput,此时运行会发现页面中显示 results 值得 Number of events 是空值,而不是初始值 0。后续输入内容后 Number of events 才会相应变化

一开始空值的原因是侦听器在初始加载时并没有运行,换言之,侦听器默认延迟加载,这可能是有用的功能

如果想侦听器在初始加载运行,可以设置选项 { immediate: true },下图可以看到 Number of events 初始值是 0,因为侦听器在初始加载时运行获得了 results 的初始值

总结:watchEffect 只需要一个参数:回调函数; 而侦听器有多个参数,包括要侦听的响应式对象和想要配置的选项。