# Composition API 初探

# 好处

Vue3 的 break changing 就是 composition api, 它打破了以往用 options 构建 Vue 实例的方式. 使用函数来代替. 这次 Vue3 升级的目的是什么呢?官方给出的解释是:

# 1. 代码组合更好

过去使用 options(data, methods, computed)来组织代码, 那么关于一个逻辑的代码就要被分散到各个 options 里面. 随着项目变得越来越大, 整个结构就会变得不可维护, 可读性也很差.

# 2. 便于逻辑分解和复用

Vue 2.x 里的 Mixins 可以为我们提供抽离逻辑并复用的功能. 可是一旦 Mixins 的数量太多, 就会导致一些问题:

  1. 命名空间冲突
  2. 模板数据来源不清晰

Vue3 将复用的逻辑以函数的形式组织起来, 在函数体里同样可以使用响应式, 生命周期钩子. 然后用 ES module 来 import 和 export(因此可以 tree shaking). 这样的写法就解决了上面 Mixins 的两个缺点, 也不会带来像高阶组件附带的额外性能开销.

# 3. 全面支持 TypeScript, 增强类型推断

Vue2.x 里面对 TS 的支持是很不友好的. Vue2.x 使用this作为上下文来暴露属性, 而this就是一个黑魔法一样的存在: methods里的this指向的不是 methods而是 Vue 实例, data里的数据不是用vm.data.xxx来访问而是直接暴露为实例的属性(vm.xxx).这些给类型推断会带来极大的麻烦. Vue2.x 的 API 也 根本不是为了类型推断而设计的. 但是从现在的前端趋势看, 去面向对象化的潮流正在兴起, 函数式编程开始崛起(React Hooks, Vue 3 Composition API). TypeScript 对函数式的支持是天然友好的. 我不知道是谁带动了谁, 但是在 2020 年, TS 和函数式是前端必学的技能.

# 起步

# 代码

<template>
  <div class="hello">
    <h1>My Event</h1>
    <p>Capacity: {{ capacity }}</p>
    <button @click="increaseCapacity()">Increase Capacity</button>
    <button @click="increment">
      Count is {{ state.count }}, double is: {{ state.double }}
    </button>
  </div>
</template>

<script>
import { ref } from "@vue/composition-api"; //tree shaking
export default {
  setup() {
    // run after beforeCreated and before created
    const capacity = ref(3); // refer to reactive reference, return a reactive object
    console.log(capacity);

    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2) // can't be primitive type
    });
    console.log("state", state);

    function increaseCapacity() {
      capacity.value++;
    }

    function increment() {
      state.count++;
    }
    return { capacity, state, increment, increaseCapacity }; // return objects & methods that template needs to access
  }
};
</script>
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
30
31
32
33
34
35
36

# 引入 composition api

import {ref, reactive, computed} from "@vue/composition-api"

为了使用 tree shaking, 这里使用 import 方式引入

# setup()

setup函数是 Vue3 新增的 hook, 它在beforeCreated之后又在created之前的阶段执行.

# ref()

调用ref()后, 传入的参数会被修改为一个 reactive object,并且添加getter,setter, 使其在改变时会被监听到变动进而更新视图. 参数也被放入到 reactive object 的value属性里. 我们在控制台可以看到capacity是什么: ref

# reactive()&computed()

reactive接受一个对象, 返回一个 reactive object. 它在内部可以使用computed(fn), 但是内部不能使用值类型. computed内部伪代码如下:

function computed(getter) {
  let value;
  watch(() => {
    value = getter();
  });
  return value;
}
1
2
3
4
5
6
7

如果是值类型, 那么computed赋值的对象就会重新使用一个内存空间存放computed的返回值. 因此, 对赋值对象的修改不会被依赖追踪发现. 而对象是引用类型, 对它的修改也会导致引用它的对象也发生变化. ref

实际上, ref()reactive()的 fallback, 由于值类型不能在reactive()里使用, 所以 Vue 提供了ref()给我们处理值类型的数据

# return

最后我们要将模板里需要的对象和方法返回, 使模板能够访问到.

# 生命周期

Vue3 里的生命周期钩子只能在setup()里使用, 使用方式是接受一个回调:

import {onMounted} from "vue"
  setup(){
    onMounted(()=>{
      console.log('component is mounted')
    })
  }
1
2
3
4
5
6