vue composition api 源码解析

3,146 阅读5分钟

前言

由于笔者最近试玩了一下供 vue2.x 版本里享受 vue3.x 开发体验的 @vue/composition-api,看到它的代码量才一千多行,很适合用来快速学习 vue3 一些新 api 的用法,和背后的思想。过完一遍后对阅读 vue-next 也很有帮助,所以有了以下这篇文章。

建议

在阅读前或者阅读过程中,可以用 vue-cli 新建一个 vue 项目,装一个compositon api玩一下。

视频参考:www.bilibili.com/video/av873… (我也是看了这个视频才开始试玩 composition api 的)

项目参考:github.com/rottenpen/v…

api参考:github.com/vuejs/compo…

RFC参考:composition-api.vuejs.org/#summary

正文

ps. 下列源码路径参考截图最上方,观众老爷们自行查找

1. createComponet/defineComponent

早几天 createComponent 已经改名叫 defineComponent 了。 实际上这个 api 只是直接 return 传进来的 options 所以说export default defineComponent({}) 是等价于export default {}

那为什么要另外定义一个这样的 API 呢

目前看来这样做的最大作用只是限制 type, setup 必须是函数,props 必须是 undefined 或者 对象。

2. setup(props)

所以setup是什么呢?

正如起名 setup 是一个初始化函数。它是一个接受 props(这里的 props 正是 defineComponent 所传的 props) 作为参数被调用于 beforeCreate 的函数。会返回一个对象,或者函数。

setup(props) {
  return {}
}

3. reactive(obj)

这里可以理解为 reactive 就是 vue2 里面的 data。传入一个对象,然后通过 observe 函数让 obj 可观察。

ps. 这里复习一下:Vue.observable 是 vue 2.6 的 api 它会调用 observe 函数,并返回该对象。

至于怎么观察对象…参考网上各种教程, 略(源码传送门⬇️) Vue2

Vue-next

4. reactivity api

这里包括了 ref(raw), computed(options), watch(source, cb, options) 三个 API ,因为它的设计与 react Hooks 很相似,所以也是被讨论的最多的 api。

这里提出的 API 和 React Hooks 有一定的相似性,具有同等的基于函数抽取和复用逻辑的能力,但也有很本质的区别。React Hooks 在每次组件渲染时都会调用,通过隐式地将状态挂载在当前的内部组件节点上,在下一次渲染时根据调用顺序取出。而 Vue 的 setup() 每个组件实例只会在初始化时调用一次 ,状态通过引用储存在 setup() 的闭包内。

⬆️ 尤大在 Vue Function-based API RFC 上的原话

ref(raw)

对标 vue2 里面 data 的单个参数(或者 react hooks 的useState) 函数传入一个 any 的参数 raw,通过调用上述 reactive(obj) 生成一个被监听对象,用于存储 raw。

computed(options) -> option = {get, set}

对标vue2里面的 computed, 返回一个 ref

请留意这里有一个很关键的函数。defineComponentInstance

Ctor 是当前的 Vue 类,用于生成新的实例。所以说这个函数是用来生成一个新的vue实例,用来生成一个被监听的 computed 对象 ?state。所以可以看到, getter 调用的是 vm.?state。

ps. 吐槽一下,computed 明明 type 是 readonly 但看源码它是有开放 设置 setter 的钩子的,如果 options 传入是一个 { set: () => {}, get => {}}

当然这样写在你对 computed 生成的 ref 赋值时,依然是不会奏效的…毕竟 vue2 的 computed 已经把 setter 禁掉了。

watch(source, cb, options)

对标 vue2 里面的 watch。有点不一样的是,watch 第一个参数 source 它可以是: 一个返回任意值的函数 一个包装对象 一个包含上述两种数据源的数组, 第二个参数 cb 是一个回调函数。 第三个参数 options

Watch 方法最后会调用 createWatcher 方法。 createWatcher 方法只返回了一个能让 watch 重新挂载的函数, 它是挂载到当前实例上去的。它主要的逻辑,引用尤大的原话⬇️:

和 2.x 的 $watch 有所不同的是,watch() 的回调会在创建时就执行一次。这有点类似 2.x watcher 的 immediate: true 选项,但有一个重要的不同:默认情况下 watch() 的回调总是会在当前的 renderer flush 之后才被调用 —— 换句话说,watch() 的回调在触发时,DOM 总是会在一个已经被更新过的状态下。 这个行为是可以通过选项来定制的。

在 2.x 的代码中,我们经常会遇到同一份逻辑需要在 mounted 和一个 watcher 的回调中执行(比如根据当前的 id 抓取数据),3.0 的 watch() 默认行为可以直接表达这样的需求。

来看看它是怎么实现上面逻辑的:

这里有一个知识点要提一下,当响应数据 发生改变后,触发 watcher.update() ,它不是马上执行callback的,而是把它推送到一个队列里,等 nextTick 后才真正执行这个callback。

新增 flush option 用来定制 cb 在更新前还是更新后触发,分为 pre | post | sync 三种状态 如果为 sync 会触发 vue2.x 里的隐藏 option -> sync : true

Sourse 发生变化时,callback将会被同步调用,而不会被推到 queuewatcher 队列里去。

如果你想在 renderer flush 之前调用可以把 flush 设置为 pre

update 前,执行 flushPreQueue 队列 update 后,执行 flushPostQueie 队列

5. 生命周期

剩下的是API 基本都是生命周期的勾子了。通过 vue.config.optionMergeStrategies 把 callback 合并到当前对应的生命周期内。

结尾

Composition api 的 api 与源码解析就讲到这里了。可能存在不少错漏,欢迎大家指导。