diff --git a/example/gogoend/index.html b/example/gogoend/index.html
new file mode 100644
index 00000000..61f97cf5
--- /dev/null
+++ b/example/gogoend/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ gogoend CompositionAPI 测试
+
+
+
+
+
+
+
diff --git a/example/gogoend/index.js b/example/gogoend/index.js
new file mode 100644
index 00000000..8d0261ea
--- /dev/null
+++ b/example/gogoend/index.js
@@ -0,0 +1,82 @@
+import Vue from 'vue'
+import VueCompositionAPI, {
+ ref,
+ createApp,
+ onMounted,
+ defineComponent,
+ watch,
+ effectScope,
+ watchEffect,
+ computed,
+ provide,
+} from '@vue/composition-api'
+
+Vue.use(VueCompositionAPI)
+
+// ----- effectScope相关开始 -----
+const scope = effectScope()
+const counter = ref(1)
+scope.run(() => {
+ const doubled = computed(() => counter.value * 2)
+
+ watch(doubled, () => console.log(doubled.value))
+
+ watchEffect(() => console.log('Count: ', doubled.value))
+})
+// 处理掉当前作用域内的所有 effect
+scope.stop()
+// ----- effectScope相关结束 -----
+
+const App = defineComponent({
+ template: `
+
+
{{ msg }} {{ msg1 }}
+
+
+`,
+ setup() {
+ const msg = ref('666')
+ console.log(msg)
+ provide(msg)
+
+ watch(
+ msg,
+ (...args) => {
+ debugger
+ console.log('w1', ...args)
+ },
+ {}
+ )
+ watch(
+ msg,
+ (...args) => {
+ debugger
+ console.log('w2', ...args)
+ },
+ {
+ immediate: true,
+ }
+ )
+ msg.value = '777'
+
+ onMounted(() => {
+ debugger
+ console.log(1)
+ })
+ onMounted(() => {
+ debugger
+ console.log(2)
+ })
+ return {
+ msg,
+ counter,
+ }
+ },
+ data() {
+ return {
+ msg1: '777',
+ }
+ },
+})
+
+createApp(App).mount('#app')
diff --git a/rollup.config.js b/rollup.config.js
index 4e8f4e3e..d8f481ad 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -79,7 +79,7 @@ function genConfig({ outFile, format, mode }) {
'process.env.NODE_ENV':
format === 'es'
? // preserve to be handled by bundlers
- 'process.env.NODE_ENV'
+ 'undefined'
: // hard coded dev/prod builds
JSON.stringify(isProd ? 'production' : 'development'),
__DEV__:
diff --git a/src/apis/computed.ts b/src/apis/computed.ts
index fc85a5ee..9923de6f 100644
--- a/src/apis/computed.ts
+++ b/src/apis/computed.ts
@@ -27,10 +27,12 @@ export function computed(
export function computed(
getterOrOptions: ComputedGetter | WritableComputedOptions
): ComputedRef | WritableComputedRef {
+ // 获得当前激活的(在作用域内的vm)
const vm = getCurrentScopeVM()
let getter: ComputedGetter
let setter: ComputedSetter | undefined
+ // 归一化getterOrOptions,将它们分别赋值给getter、setter
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
} else {
@@ -41,19 +43,25 @@ export function computed(
let computedSetter
let computedGetter
+ // 如果能拿到vm,并且在非服务器渲染环境下
if (vm && !vm.$isServer) {
+ // 获得Vue内部的Watcher及Dep类
const { Watcher, Dep } = getVueInternalClasses()
let watcher: any
computedGetter = () => {
if (!watcher) {
+ // 设置计算属性Watcher —— 逻辑与传统的Vue 2主项目一致
watcher = new Watcher(vm, getter, noopFn, { lazy: true })
}
+ // 如果watcher dirty了,就重新进行一次计算来获取新的值
if (watcher.dirty) {
watcher.evaluate()
}
+ // TODO: 如果存在全剧唯一正在被计算的watcher,那么就进行以来收集
if (Dep.target) {
watcher.depend()
}
+ // 返回计算属性watcher的值
return watcher.value
}
@@ -67,7 +75,9 @@ export function computed(
setter(v)
}
}
- } else {
+ }
+ // 否则,创建一个新的vue实例,来托管computed
+ else {
// fallback
const computedHost = defineComponentInstance(getVueConstructor(), {
computed: {
@@ -91,6 +101,7 @@ export function computed(
}
}
+ // 返回一个用于获得computed值的ref
return createRef(
{
get: computedGetter,
diff --git a/src/apis/effectScope.ts b/src/apis/effectScope.ts
index cde52c5c..ee9466d4 100644
--- a/src/apis/effectScope.ts
+++ b/src/apis/effectScope.ts
@@ -7,6 +7,9 @@ import {
import { defineComponentInstance } from '../utils'
import { warn } from './warn'
+/**
+ * 正在活动(全局唯一)的effectScope
+ */
let activeEffectScope: EffectScope | undefined
const effectScopeStack: EffectScope[] = []
@@ -75,12 +78,20 @@ export class EffectScope extends EffectScopeImpl {
}
}
+/**
+ * 记录effectScope?
+ * @param effect
+ * @param scope
+ * @returns
+ */
export function recordEffectScope(
effect: EffectScope,
scope?: EffectScope | null
) {
scope = scope || activeEffectScope
if (scope && scope.active) {
+ // 如果不是游离的Effect,那么就往当前scope effects里push一下传入的effect
+ // 看起来有点像一棵树
scope.effects.push(effect)
return
}
@@ -109,6 +120,7 @@ export function onScopeDispose(fn: () => void) {
}
/**
+ * 获得当前在scope内的vm?TODO: 为何有两种写法?
* @internal
**/
export function getCurrentScopeVM() {
@@ -116,14 +128,18 @@ export function getCurrentScopeVM() {
}
/**
+ * 绑定当前scope到vm上?
* @internal
**/
export function bindCurrentScopeToVM(
vm: ComponentInternalInstance
): EffectScope {
+ // 如果vm上没有scope,就设置一下
if (!vm.scope) {
const scope = new EffectScopeImpl(vm.proxy) as EffectScope
vm.scope = scope
+
+ // vm销毁的时候,scope给停掉
vm.proxy.$on('hook:destroyed', () => scope.stop())
}
return vm.scope
diff --git a/src/apis/lifecycle.ts b/src/apis/lifecycle.ts
index 3bb49e3a..3a43d306 100644
--- a/src/apis/lifecycle.ts
+++ b/src/apis/lifecycle.ts
@@ -8,39 +8,77 @@ import {
import { getCurrentInstanceForFn } from '../utils/helper'
const genName = (name: string) => `on${name[0].toUpperCase() + name.slice(1)}`
+
+/**
+ * 生命周期工厂函数?
+ * @param lifeCyclehook 生命周期的名称
+ * @returns
+ */
function createLifeCycle(lifeCyclehook: string) {
- return (callback: Function, target?: ComponentInternalInstance | null) => {
- const instance = getCurrentInstanceForFn(genName(lifeCyclehook), target)
- return (
- instance &&
- injectHookOption(getVueConstructor(), instance, lifeCyclehook, callback)
- )
- }
+ return (
+ /**
+ * 在setup函数执行期间,在调用生命周期钩子(如onMounted等)将会执行的函数 - 该函数用于合并生命周期钩子
+ * @param callback 生命周期回调函数
+ * @param target Vue3 对象
+ * @returns
+ */
+ (callback: Function, target?: ComponentInternalInstance | null) => {
+ const instance = getCurrentInstanceForFn(genName(lifeCyclehook), target)
+ return (
+ instance &&
+ injectHookOption(getVueConstructor(), instance, lifeCyclehook, callback)
+ )
+ }
+ )
}
+/**
+ * 合并生命周期钩子
+ * @param Vue Vue构造函数
+ * @param instance 当前实例(Vue3)
+ * @param hook 生命周期钩子名称
+ * @param val 值(生命周期回调函数)
+ * @returns
+ */
function injectHookOption(
Vue: VueConstructor,
instance: ComponentInternalInstance,
hook: string,
val: Function
) {
+ // 当前实例中的选项
const options = instance.proxy.$options as Record
+ // 获得钩子的合并策略 - 见Vue2源代码 `mergeHook`
const mergeFn = Vue.config.optionMergeStrategies[hook]
+ // 获得一个经过包裹的回调函数
const wrappedHook = wrapHookCall(instance, val)
+ // 将经过合并的钩子重新赋值给到组件option
+ // options[hook] 是个数组,组件生命周期的执行实际上是挨个执行其中的函数
+ // 每调用一次对应的onHook(eg. onMounted)都会往对应数组里加一个函数
options[hook] = mergeFn(options[hook], wrappedHook)
return wrappedHook
}
+/**
+ * 获得一个经过包裹的回调函数 - 经过包裹后,回调函数执行期间,将确保正在激活的组件实例是当前实例(Vue3)
+ * @param instance 当前实例(Vue3)
+ * @param fn 生命周期钩子中的回调函数
+ * @returns 经过包裹后的回调函数
+ */
function wrapHookCall(
instance: ComponentInternalInstance,
fn: Function
): Function {
return (...args: any) => {
+ // 保存上一个正在激活状态的组件(Vue3)
let prev = getCurrentInstance()
+ // 设置激活状态组件为当前组件(Vue3)
setCurrentInstance(instance)
try {
+ // 执行生命周期钩子的回调函数
return fn(...args)
} finally {
+ // 恢复上一个正在激活状态的组件(Vue3)
setCurrentInstance(prev)
}
}
diff --git a/src/apis/watch.ts b/src/apis/watch.ts
index b3e0984c..bc0e6d38 100644
--- a/src/apis/watch.ts
+++ b/src/apis/watch.ts
@@ -70,6 +70,8 @@ export interface VueWatcher {
export type WatchStopHandle = () => void
+// 用于回落的vm
+// getWatcherVM、createScheduler会使用
let fallbackVM: ComponentInstance
function flushPreQueue(this: any) {
@@ -80,17 +82,38 @@ function flushPostQueue(this: any) {
flushQueue(this, WatcherPostFlushQueueKey)
}
+/**
+ * 检查vm中是否存在composotion-api queue专用的属性
+ * @param vm
+ * @returns
+ */
function hasWatchEnv(vm: any) {
return vm[WatcherPreFlushQueueKey] !== undefined
}
+/**
+ * 初始化vm watch的环境
+ * 看起来做了下面两件事:
+ * 1. 在vm中加入WatcherPreFlushQueueKey、WatcherPostFlushQueueKey —— 它们对应的值是数组
+ * 2. 设置在beforeUpdate、updated生命周期将要做的事情,即分别冲刷对应队列
+ *
+ * @param vm Vue组件
+ */
function installWatchEnv(vm: any) {
+ // 加入WatcherPreFlushQueueKey、WatcherPostFlushQueueKey属性,并设置值为数组的逻辑
vm[WatcherPreFlushQueueKey] = []
vm[WatcherPostFlushQueueKey] = []
+
+ // 设置组件beforeUpdate、updated生命周期执行前后,冲刷一次队列的逻辑
vm.$on('hook:beforeUpdate', flushPreQueue)
vm.$on('hook:updated', flushPostQueue)
}
+/**
+ * 合并watcher选项
+ * @param options watcher的选项
+ * @returns
+ */
function getWatcherOption(options?: Partial): WatchOptions {
return {
...{
@@ -102,6 +125,11 @@ function getWatcherOption(options?: Partial): WatchOptions {
}
}
+/**
+ * 合并watch Effect的选项
+ * @param options watchEffect的选项
+ * @returns
+ */
function getWatchEffectOption(options?: Partial): WatchOptions {
return {
...{
@@ -111,52 +139,85 @@ function getWatchEffectOption(options?: Partial): WatchOptions {
}
}
+/**
+ * 获得当前正在激活的Vue实例
+ * @returns
+ */
function getWatcherVM() {
let vm = getCurrentScopeVM()
+ // 如果当前vm不存在,就生成一个新的vm
if (!vm) {
if (!fallbackVM) {
fallbackVM = defineComponentInstance(getVueConstructor())
}
vm = fallbackVM
- } else if (!hasWatchEnv(vm)) {
+ }
+ // 如果vm中不存在composition-api queue专用的属性,就安装一下
+ else if (!hasWatchEnv(vm)) {
installWatchEnv(vm)
}
return vm
}
+/**
+ * 冲刷队列,冲刷结束后将队列长度设置为0
+ * @param vm
+ * @param key
+ */
function flushQueue(vm: any, key: any) {
const queue = vm[key]
for (let index = 0; index < queue.length; index++) {
queue[index]()
}
+ // 队列冲刷完了以后,将队列长度设置为0
queue.length = 0
}
+/**
+ * 将任务排到冲刷队列中,并在nextTick后冲刷掉所有任务
+ * @param vm
+ * @param fn 回调函数
+ * @param mode 清空模式
+ */
function queueFlushJob(
vm: any,
fn: () => void,
mode: Exclude
) {
// flush all when beforeUpdate and updated are not fired
+ /**
+ * 即使 beforeUpdate 与 updated 未触发,也冲刷一次队列
+ *
+ * TODO: 正常情况下,队列应该是 beforeUpdate 与 updated 执行时会冲刷一次的
+ * 目前来看即使没有走这两个生命周期,也会冲刷一次队列
+ */
const fallbackFlush = () => {
+ // 真正的冲刷逻辑是在nextTick后才执行的,在此之前可以往队列里排任务
vm.$nextTick(() => {
+ // beforeUpdate生命周期之前冲刷
if (vm[WatcherPreFlushQueueKey].length) {
flushQueue(vm, WatcherPreFlushQueueKey)
}
+ // update生命周期之后冲刷
if (vm[WatcherPostFlushQueueKey].length) {
flushQueue(vm, WatcherPostFlushQueueKey)
}
})
}
+ /**
+ * 预先确定好在nextTick时冲刷一次队列,并在此之前排任务的逻辑
+ *
+ * TODO: 真正冲刷的时候似乎就不管flush值是post还是pre了,只按照排序顺序执行
+ */
switch (mode) {
case 'pre':
- fallbackFlush()
- vm[WatcherPreFlushQueueKey].push(fn)
+ fallbackFlush() // 预定好在nextTick时冲刷一次队列
+ vm[WatcherPreFlushQueueKey].push(fn) // 在真正的冲刷逻辑执行此之前,我们可以把任务先排上
break
case 'post':
- fallbackFlush()
- vm[WatcherPostFlushQueueKey].push(fn)
+ fallbackFlush() // 预定好在nextTick时冲刷一次队列
+ vm[WatcherPostFlushQueueKey].push(fn) // 在真正的冲刷逻辑执行此之前,我们可以把任务先排上
break
default:
assert(
@@ -167,6 +228,16 @@ function queueFlushJob(
}
}
+/**
+ * 创建Watcher - 实际上是对vm.$watch的封装
+ * 由createWatcher在内部进行调用
+ *
+ * @param vm 当前实例
+ * @param getter 监听源 - 这里似乎传入的是一个函数
+ * @param callback 回调函数
+ * @param options 选项
+ * @returns 被设置好的watcher
+ */
function createVueWatcher(
vm: ComponentInstance,
getter: () => any,
@@ -180,6 +251,7 @@ function createVueWatcher(
}
): VueWatcher {
const index = vm._watchers.length
+ // 这里会往vm._watchers中插入新设置的watcher
// @ts-ignore: use undocumented options
vm.$watch(getter, callback, {
immediate: options.immediateInvokeCallback,
@@ -194,20 +266,42 @@ function createVueWatcher(
// We have to monkeypatch the teardown function so Vue will run
// runCleanup() when it tears down the watcher on unmounted.
+/**
+ * 将watcher中的teardown方法进行一些额外处理,使得Vue能够在卸载时运行runCleanup()
+ *
+ * monkeypatch 即运行时动态替换
+ *
+ * @param watcher Watcher
+ * @param runCleanup runCleanup
+ */
function patchWatcherTeardown(watcher: VueWatcher, runCleanup: () => void) {
+ // 保留原有teardown方法
const _teardown = watcher.teardown
+ // 将原有teardown方法替换为如下方法
watcher.teardown = function (...args) {
+ // 首先执行原有方法
_teardown.apply(watcher, args)
+ // 然后再执行runCleanup
runCleanup()
}
}
+/**
+ * 创建watcher 由watch、watchEffect在内部进行调用
+ *
+ * @param vm 当前实例
+ * @param source 监听源
+ * @param cb 回调函数
+ * @param options 选项
+ * @returns 一个用于关闭watcher的函数
+ */
function createWatcher(
vm: ComponentInstance,
source: WatchSource | WatchSource[] | WatchEffect,
cb: WatchCallback | null,
options: WatchOptions
): () => void {
+ // cb 未传入,但传了immediate、deep时将会报错
if (__DEV__ && !cb) {
if (options.immediate !== undefined) {
warn(
@@ -223,13 +317,20 @@ function createWatcher(
}
}
+ // 冲刷模式
const flushMode = options.flush
+ // 是否同步地进行冲刷标识
const isSync = flushMode === 'sync'
+ //#region 清理函数定义、设置
let cleanup: (() => void) | null
+ /**
+ * 设置cleanup函数
+ * @param fn
+ */
const registerCleanup: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = () => {
try {
- fn()
+ fn() // TODO: 好像没有任何地方有传入fn这个参数?
} catch (
// FIXME: remove any
error: any
@@ -239,20 +340,32 @@ function createWatcher(
}
}
// cleanup before running getter again
+ /**
+ * 在getter回调执行之前,进行一次清理
+ */
const runCleanup = () => {
if (cleanup) {
cleanup()
cleanup = null
}
}
+ //#endregion
+
+ /**
+ * 创建调度器
+ *
+ * 如果watch配置中,isSync为true,或者当前没有正在激活的vm实例,那么就返回函数本身,即认为任务(?)不必进入冲刷队列,应当立即执行
+ * 否则返回一个将当前函数插入到冲刷队列中的函数,即认为任务(?)应当延后执行
+ **/
const createScheduler = (fn: T): T => {
if (
isSync ||
- /* without a current active instance, ignore pre|post mode */ vm ===
- fallbackVM
+ /* without a current active instance, ignore pre|post mode */
+ /* 没有正在激活的vm实例的情况下,忽略 pre|post 配置 */ vm === fallbackVM
) {
return fn
}
+ // 否则加入冲刷队列
return ((...args: any[]) =>
queueFlushJob(
vm,
@@ -264,6 +377,7 @@ function createWatcher(
}
// effect watch
+ // TODO: 没有cb,说明是effect watch?
if (cb === null) {
let running = false
const getter = () => {
@@ -293,6 +407,7 @@ function createWatcher(
// always run watchEffect
watcher.get = createScheduler(originGet)
+ // 直接返回,不必再做其他事情了
return () => {
watcher.teardown()
}
@@ -302,14 +417,20 @@ function createWatcher(
let isMultiSource = false
let getter: () => any
+ // 如果是ref则返回其中的value
if (isRef(source)) {
getter = () => source.value
- } else if (isReactive(source)) {
+ }
+ // 如果经过reactive处理,就返回这个值
+ else if (isReactive(source)) {
getter = () => source
- deep = true
- } else if (isArray(source)) {
+ deep = true // TODO: 为什么要把deep设为true?或许,因为source是个对象,因此deep应该设置为true;或许应该只观测其中对象第一层的变化?
+ }
+ // 如果是个数组,那就说明是有多个监听源
+ else if (isArray(source)) {
isMultiSource = true
getter = () =>
+ // 因此对这些监听源再各自处理一次
source.map((s) => {
if (isRef(s)) {
return s.value
@@ -327,9 +448,13 @@ function createWatcher(
return noopFn
}
})
- } else if (isFunction(source)) {
+ }
+ // 如果是个函数
+ else if (isFunction(source)) {
getter = source as () => any
- } else {
+ }
+ // 其它神经的情况 - watch源无效
+ else {
getter = noopFn
__DEV__ &&
warn(
@@ -339,14 +464,24 @@ function createWatcher(
)
}
+ // TODO: 如果deep为true的话,就遍历一下整个对象,重新设置getter?
if (deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
+ /**
+ * 我们所传入的watcher回调由此函数调用
+ *
+ * @param n 旧值
+ * @param o 新值
+ * @returns 我们所传入的callback的返回值
+ */
const applyCb = (n: any, o: any) => {
if (
+ // TODO: 不是deep就不用调用了?
!deep &&
+ // 如果监听源是个数组且每一项都相同,也不必调用了
isMultiSource &&
n.every((v: any, i: number) => isSame(v, o[i]))
)
@@ -355,9 +490,16 @@ function createWatcher(
runCleanup()
return cb(n, o, registerCleanup)
}
+ /**
+ * callback即最终要设置的、$watch的参数
+ */
let callback = createScheduler(applyCb)
+ // TODO: 立即调用时的逻辑
if (options.immediate) {
+ // 存一下原始的callback
const originalCallback = callback
+ // shiftCallback用来处理第一次同步的副作用执行
+ // 在第一次执行过后,shiftCallback的值将变为原来的callback,此时即可恢复主线的watcher回调函数
// `shiftCallback` is used to handle the first sync effect run.
// The subsequent callbacks will redirect to `callback`.
let shiftCallback = (n: any, o: any) => {
@@ -370,6 +512,7 @@ function createWatcher(
}
}
+ // 停止监听
// @ts-ignore: use undocumented option "sync"
const stop = vm.$watch(getter, callback, {
immediate: options.immediate,
@@ -392,13 +535,23 @@ function createWatcher(
})
}
+ // 将watcher中的teardown方法进行一些额外处理,使得Vue能够在卸载时运行runCleanup()
patchWatcherTeardown(watcher, runCleanup)
+ // 返回一个函数,用于停止监听
return () => {
stop()
}
}
+/**
+ * watchEffect 函数入口
+ *
+ * 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
+ * @param effect 函数
+ * @param options 监听选项 - 对于watchEffect来说只有flush可配置
+ * @returns
+ */
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
@@ -416,6 +569,7 @@ export function watchSyncEffect(effect: WatchEffect) {
return watchEffect(effect, { flush: 'sync' })
}
+//#region watch 函数定义与实现
// overload #1: array of multiple sources + cb
// Readonly constraint helps the callback to correctly infer value types based
// on position in the source array. Otherwise the values will get a union type
@@ -447,6 +601,14 @@ export function watch<
): WatchStopHandle
// implementation
+/**
+ * watch 函数入口
+ *
+ * @param source 监听源
+ * @param cb 回调函数
+ * @param options 监听选项
+ * @returns
+ */
export function watch(
source: WatchSource | WatchSource[],
cb: WatchCallback,
@@ -472,8 +634,10 @@ export function watch(
const opts = getWatcherOption(options)
const vm = getWatcherVM()
+ // 真正的创建watcher的逻辑
return createWatcher(vm, source, callback, opts)
}
+//#endregion
function traverse(value: unknown, seen: Set = new Set()) {
if (!isObject(value) || seen.has(value) || rawSet.has(value)) {
diff --git a/src/install.ts b/src/install.ts
index fd8a56e6..4cfeafc6 100644
--- a/src/install.ts
+++ b/src/install.ts
@@ -7,6 +7,7 @@ import { mixin } from './mixin'
/**
* Helper that recursively merges two data objects together.
+ * 递归地合并两个data对象?
*/
function mergeData(from: AnyObject, to: AnyObject): Object {
if (!from) return to
@@ -40,6 +41,7 @@ function mergeData(from: AnyObject, to: AnyObject): Object {
}
export function install(Vue: VueConstructor) {
+ // 防止被插件被多次安装
if (isVueRegistered(Vue)) {
if (__DEV__) {
warn(
@@ -61,6 +63,7 @@ export function install(Vue: VueConstructor) {
}
}
+ // 配置setup的合并策略
Vue.config.optionMergeStrategies.setup = function (
parent: Function,
child: Function
@@ -73,7 +76,9 @@ export function install(Vue: VueConstructor) {
}
}
+ // 在Vue类上记录一个表示插件已安装的标志位
setVueConstructor(Vue)
+ // 配置全局mixin
mixin(Vue)
}
diff --git a/src/mixin.ts b/src/mixin.ts
index dc100fda..6c897673 100644
--- a/src/mixin.ts
+++ b/src/mixin.ts
@@ -31,12 +31,14 @@ export function mixin(Vue: VueConstructor) {
Vue.mixin({
beforeCreate: functionApiInit,
mounted(this: ComponentInstance) {
+ // 内部逻辑:保持ref引用的对应DOM/组件为最新
afterRender(this)
},
beforeUpdate() {
updateVmAttrs(this as ComponentInstance)
},
updated(this: ComponentInstance) {
+ // 内部逻辑:保持ref引用的对应DOM/组件为最新
afterRender(this)
},
})
@@ -48,8 +50,11 @@ export function mixin(Vue: VueConstructor) {
function functionApiInit(this: ComponentInstance) {
const vm = this
const $options = vm.$options
+
+ // 保存原有option上的setup、render方法
const { setup, render } = $options
+ // 如果render函数存在,就不用管setup函数了
if (render) {
// keep currentInstance accessible for createElement
$options.render = function (...args: any): any {
@@ -72,10 +77,14 @@ export function mixin(Vue: VueConstructor) {
return
}
+ // 保存原有option上的data方法
const { data } = $options
// wrapper the data option, so we can invoke setup before data get resolved
+ // 相当于把setup包装成data函数
$options.data = function wrappedData() {
+ // 初始化setup函数中返回的data
initSetup(vm, vm.$props)
+ // 初始化data选项中的data
return isFunction(data)
? (
data as (this: ComponentInstance, x: ComponentInstance) => object
@@ -85,24 +94,28 @@ export function mixin(Vue: VueConstructor) {
}
function initSetup(vm: ComponentInstance, props: Record = {}) {
- const setup = vm.$options.setup!
+ const setup = vm.$options.setup! // setup函数必然存在
const ctx = createSetupContext(vm)
const instance = toVue3ComponentInstance(vm)
instance.setupContext = ctx
+ // 给props设置响应式?
// fake reactive for `toRefs(props)`
def(props, '__ob__', createObserver())
// resolve scopedSlots and slots to functions
resolveScopedSlots(vm, ctx.slots)
+ // binding 即 setup 的返回值
let binding: ReturnType> | undefined | null
activateCurrentInstance(instance, () => {
// make props to be fake reactive, this is for `toRefs(props)`
+ // 执行我们传入的setup函数的地方,binding是setup的返回值
binding = setup(props, ctx)
})
if (!binding) return
+ // 如果返回值是一个函数的话,看起来应该是个渲染函数
if (isFunction(binding)) {
// keep typescript happy with the binding type.
const bindingFunc = binding
@@ -112,36 +125,51 @@ export function mixin(Vue: VueConstructor) {
return activateCurrentInstance(instance, () => bindingFunc())
}
return
- } else if (isObject(binding)) {
+ }
+ // 如果返回的是一个纯对象
+ else if (isObject(binding)) {
if (isReactive(binding)) {
- binding = toRefs(binding) as Data
+ binding = toRefs(binding) as Data // 需要把这个对象进行响应式处理
}
+ // 这里对vm进行了一点修改,为vm加了__composition_api_state__属性
vmStateManager.set(vm, 'rawBindings', binding)
const bindingObj = binding
+ // 统一bindingValue的格式并赋值
+ // 已经是ref或响应式对象的值不做处理;
+ // 基本类型的值与数组转为ref
+ // TODO: 好像没考虑对象?
Object.keys(bindingObj).forEach((name) => {
let bindingValue: any = bindingObj[name]
+ // 如果遍历访问到的值不是一个ref
if (!isRef(bindingValue)) {
+ // 如果遍历访问到的值也不是一个响应式对象
if (!isReactive(bindingValue)) {
+ // 如果这个值为函数
if (isFunction(bindingValue)) {
const copy = bindingValue
bindingValue = bindingValue.bind(vm)
Object.keys(copy).forEach(function (ele) {
bindingValue[ele] = copy[ele]
})
- } else if (!isObject(bindingValue)) {
+ }
+ // 如果这个值不是对象的话,需要用ref包一下
+ else if (!isObject(bindingValue)) {
bindingValue = ref(bindingValue)
} else if (hasReactiveArrayChild(bindingValue)) {
// creates a custom reactive properties without make the object explicitly reactive
// NOTE we should try to avoid this, better implementation needed
customReactive(bindingValue)
}
- } else if (isArray(bindingValue)) {
+ }
+ // 如果这个值是一个数组的话,也需要用ref包一下
+ else if (isArray(bindingValue)) {
bindingValue = ref(bindingValue)
}
}
+ // 把bindingValue作为属性放到vm上,这样模板里也可以访问到setup中的属性
asVmProperty(vm, name, bindingValue)
})
diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts
index 797a6cee..2d192586 100644
--- a/src/reactivity/reactive.ts
+++ b/src/reactivity/reactive.ts
@@ -111,12 +111,21 @@ export function defineAccessControl(target: AnyObject, key: any, val?: any) {
})
}
+/**
+ * 返回一个经过响应式处理的对象
+ * 该函数被shallowReactive、reactive、shallowReadonly所使用
+ * @param obj 对象
+ * @returns
+ */
export function observe(obj: T): T {
const Vue = getRegisteredVueOrDefault()
let observed: T
+ // 低版本Vue中不存在observable这个方法,在这里做了兼容
if (Vue.observable) {
+ // 显式调用vue中用于监听对象的observe函数
observed = Vue.observable(obj)
} else {
+ // 兼容处理看起来是生成了一个新的vue实例
const vm = defineComponentInstance(Vue, {
data: {
$$state: obj,
@@ -168,10 +177,15 @@ function mockObserver(value: any = {}): any {
}
}
+// 仅仅是用于获得__ob__对象
export function createObserver() {
return observe({}).__ob__
}
+/**
+ * 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)
+ * @param obj
+ */
export function shallowReactive(obj: T): T
export function shallowReactive(obj: any) {
if (!isObject(obj)) {
@@ -229,6 +243,8 @@ export function shallowReactive(obj: any) {
/**
* Make obj reactivity
+ *
+ * 返回对象的响应式副本
*/
export function reactive(obj: T): UnwrapRef {
if (!isObject(obj)) {
@@ -252,6 +268,7 @@ export function reactive(obj: T): UnwrapRef {
}
/**
+ * 确保对象不被响应式处理
* Make sure obj can't be a reactive
*/
export function markRaw(obj: T): T {
diff --git a/src/reactivity/readonly.ts b/src/reactivity/readonly.ts
index 537e8e74..48a90236 100644
--- a/src/reactivity/readonly.ts
+++ b/src/reactivity/readonly.ts
@@ -52,6 +52,10 @@ export function readonly(
return target as any
}
+/**
+ * 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
+ * @param obj
+ */
export function shallowReadonly(obj: T): Readonly
export function shallowReadonly(obj: any): any {
if (!isObject(obj)) {
diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts
index 4da6a55f..f4f60dbb 100644
--- a/src/reactivity/ref.ts
+++ b/src/reactivity/ref.ts
@@ -6,6 +6,7 @@ import { set } from './set'
import { setForceTrigger } from './force'
declare const _refBrand: unique symbol
+// 实现了Ref的对象中,包含一个value,用于存储原始值
export interface Ref {
readonly [_refBrand]: true
value: T
@@ -62,10 +63,13 @@ interface RefOption {
get(): T
set?(x: T): void
}
+
+// RefImpl实现了Ref接口
export class RefImpl implements Ref {
readonly [_refBrand]!: true
public value!: T
constructor({ get, set }: RefOption) {
+ // 内部使用Object.defineProperty,配置this.value,以代理get、set的动作
proxy(this, 'value', {
get,
set,
@@ -78,6 +82,7 @@ export function createRef(
isReadonly = false,
isComputed = false
): RefImpl {
+ // 新建一个RefImpl实例
const r = new RefImpl(options)
// add effect to differentiate refs from computed
@@ -99,11 +104,14 @@ export function ref(
export function ref(raw: T): Ref>
export function ref(): Ref
export function ref(raw?: unknown) {
+ // 如果对象已经是ref,就不再转换了,直接返回
if (isRef(raw)) {
return raw
}
+ // 最终ref里的值通过这个变量来存
const value = reactive({ [RefKey]: raw })
+ // createRef -> new RefImpl 获得响应式对象
return createRef({
get: () => value[RefKey] as any,
set: (v) => ((value[RefKey] as any) = v),
@@ -125,6 +133,7 @@ export function toRefs(obj: T): ToRefs {
if (!isPlainObject(obj)) return obj
const ret: any = {}
+ // 遍历对象中每个key,逐个转换为ref
for (const key in obj) {
ret[key] = toRef(obj, key)
}
@@ -158,8 +167,10 @@ export function toRef(
): Ref {
if (!(key in object)) set(object, key, undefined)
const v = object[key]
+ // 如果对象已经是ref,就不再转换了,直接返回
if (isRef(v)) return v
+ // 创建Ref
return createRef({
get: () => object[key],
set: (v) => (object[key] = v),
diff --git a/src/runtimeContext.ts b/src/runtimeContext.ts
index cc43b5ad..a240f20c 100644
--- a/src/runtimeContext.ts
+++ b/src/runtimeContext.ts
@@ -11,6 +11,9 @@ import {
} from './utils'
import type Vue$1 from 'vue'
+/**
+ * vue 依赖(Vue)
+ */
let vueDependency: VueConstructor | undefined = undefined
try {
@@ -29,24 +32,43 @@ try {
}
let vueConstructor: VueConstructor | null = null
+
+/**
+ * 当前Vue3实例
+ */
let currentInstance: ComponentInternalInstance | null = null
let currentInstanceTracking = true
+/**
+ * 插件已安装标识
+ */
const PluginInstalledFlag = '__composition_api_installed__'
function isVue(obj: any): obj is VueConstructor {
return obj && isFunction(obj) && obj.name === 'Vue'
}
+/**
+ * 检查插件是否已安装 - TODO: 通过判断vueConstructor是否存在?
+ * @returns 插件是否已安装
+ */
export function isPluginInstalled() {
return !!vueConstructor
}
+/**
+ * 检查插件是否已注册 - 通过判断vueConstructor是否存在 且 传入的Vue构造函数中是否具有PluginInstalledFlag
+ * @returns 插件是否已注册
+ */
export function isVueRegistered(Vue: VueConstructor) {
// resolve issue: https://github.com/vuejs/composition-api/issues/876#issue-1087619365
return vueConstructor && hasOwn(Vue, PluginInstalledFlag)
}
+/**
+ * 获得Vue构造函数
+ * @returns Vue构造函数
+ */
export function getVueConstructor(): VueConstructor {
if (__DEV__) {
assert(
@@ -69,6 +91,10 @@ export function getRegisteredVueOrDefault(): VueConstructor {
return constructor!
}
+/**
+ * 在Vue类上记录一个表示插件已安装的标志位
+ * @param Vue
+ */
export function setVueConstructor(Vue: VueConstructor) {
// @ts-ignore
if (__DEV__ && vueConstructor && Vue.__proto__ !== vueConstructor.__proto__) {
@@ -96,15 +122,25 @@ export function withCurrentInstanceTrackingDisabled(fn: () => void) {
}
}
+/**
+ * 设置当前Vue2实例 - 内部调用toVue3ComponentInstance转换
+ * @param vm
+ * @returns
+ */
export function setCurrentVue2Instance(vm: ComponentInstance | null) {
if (!currentInstanceTracking) return
setCurrentInstance(vm ? toVue3ComponentInstance(vm) : vm)
}
+/**
+ * 设置当前实例
+ */
export function setCurrentInstance(instance: ComponentInternalInstance | null) {
if (!currentInstanceTracking) return
+ // 关闭上一个实例的scope
const prev = currentInstance
prev?.scope.off()
+ // 开启当前实例的scope
currentInstance = instance
currentInstance?.scope.on()
}
@@ -178,6 +214,8 @@ export interface SetupContext {
export interface ComponentPublicInstance {}
/**
+ * 我们暴露了在内部实例上的一部分属性,因为它们对于高级的库与工具来说会较为有用。
+ *
* We expose a subset of properties on the internal instance as they are
* useful for advanced external libraries and tools.
*/
@@ -212,6 +250,9 @@ export declare interface ComponentInternalInstance {
slots: InternalSlots
emitted: Record | null
+ /**
+ * Vue2 组件实例
+ */
proxy: ComponentInstance
isMounted: boolean
@@ -229,6 +270,11 @@ export declare interface ComponentInternalInstance {
setupContext: SetupContext | null
}
+/**
+ * 获取当前实例
+ * 仅在组件setup以及生命周期函数期间能够拿到当前实例
+ * @returns
+ */
export function getCurrentInstance() {
return currentInstance
}
@@ -238,6 +284,11 @@ const instanceMapCache = new WeakMap<
ComponentInternalInstance
>()
+/**
+ * 将Vue2组件转换到Vue3组件
+ * @param vm Vue2 组件实例
+ * @returns Vue3 组件实例
+ */
export function toVue3ComponentInstance(
vm: ComponentInstance
): ComponentInternalInstance {
@@ -245,6 +296,7 @@ export function toVue3ComponentInstance(
return instanceMapCache.get(vm)!
}
+ // 内部instance
const instance: ComponentInternalInstance = {
proxy: vm,
update: vm.$forceUpdate,
@@ -258,6 +310,8 @@ export function toVue3ComponentInstance(
root: null!, // to be immediately set
} as unknown as ComponentInternalInstance
+ // 将当前作用域绑定到vue3实例上
+ // EffectScope入口
bindCurrentScopeToVM(instance)
// map vm.$props =
@@ -270,6 +324,7 @@ export function toVue3ComponentInstance(
'slots',
] as const
+ // 代理一些属性到内部instance上
instanceProps.forEach((prop) => {
proxy(instance, prop, {
get() {
diff --git a/src/utils/helper.ts b/src/utils/helper.ts
index 24b93aad..78e0b818 100644
--- a/src/utils/helper.ts
+++ b/src/utils/helper.ts
@@ -23,6 +23,12 @@ export function getCurrentInstanceForFn(
return target
}
+/**
+ * 生成一个组件实例
+ * @param Ctor 构造函数
+ * @param options 组件选项
+ * @returns
+ */
export function defineComponentInstance(
Ctor: VueConstructor,
options: ComponentOptions = {}
@@ -90,6 +96,10 @@ let vueInternalClasses:
}
| undefined
+/**
+ * 通过新构造一个Vue实例的方式,获得Vue中内部的两个类 —— Watcher、Dep,然后把它们缓存起来
+ * @returns
+ */
export const getVueInternalClasses = () => {
if (!vueInternalClasses) {
const vm: any = defineComponentInstance(getVueConstructor(), {
diff --git a/src/utils/instance.ts b/src/utils/instance.ts
index 9a5be538..3d99a9b1 100644
--- a/src/utils/instance.ts
+++ b/src/utils/instance.ts
@@ -13,13 +13,19 @@ import { hasOwn, proxy, warn } from './utils'
import { createSlotProxy, resolveSlots } from './helper'
import { reactive } from '../reactivity/reactive'
+/**
+ * 在vm上赋值属性
+ * 例如:将setup返回值上的属性代理到vm上;内部用defineProperty get、set 实现
+ */
export function asVmProperty(
vm: ComponentInstance,
propName: string,
propValue: Ref
) {
const props = vm.$options.props
+ // 如果vm中不存在名为`propName`的属性,且vm的props选项中也没有名为`propName`
if (!(propName in vm) && !(props && hasOwn(props, propName))) {
+ // 如果值被ref包过
if (isRef(propValue)) {
proxy(vm, propName, {
get: () => propValue.value,
@@ -27,10 +33,13 @@ export function asVmProperty(
propValue.value = val
},
})
- } else {
+ }
+ // 如果值没有被ref包过(可能是响应式对象?)
+ else {
proxy(vm, propName, {
get: () => {
if (isReactive(propValue)) {
+ // 进行一次依赖收集,使得watcher依赖这个值?
;(propValue as any).__ob__.dep.depend()
}
return propValue
@@ -42,6 +51,7 @@ export function asVmProperty(
}
if (__DEV__) {
+ // 开发环境下会把setup函数返回的值往_data上存一份,因此开发者工具中可以看到相关属性
// expose binding to Vue Devtool as a data property
// delay this until state has been resolved to prevent repeated works
vm.$nextTick(() => {
@@ -65,7 +75,9 @@ export function asVmProperty(
}
})
}
- } else if (__DEV__) {
+ }
+ // 如果来到这里,说明`propName`与vm的props重名了
+ else if (__DEV__) {
if (props && hasOwn(props, propName)) {
warn(
`The setup binding property "${propName}" is already declared as a prop.`,
@@ -77,6 +89,11 @@ export function asVmProperty(
}
}
+/**
+ * 来更新 setupState (aka. rawBindings)中对元素/组件的引用(ref="xxx")到最新的渲染结果
+ * @param vm
+ * @returns
+ */
function updateTemplateRef(vm: ComponentInstance) {
const rawBindings = vmStateManager.get(vm, 'rawBindings') || {}
if (!rawBindings || !Object.keys(rawBindings).length) return
@@ -104,6 +121,11 @@ function updateTemplateRef(vm: ComponentInstance) {
vmStateManager.set(vm, 'refs', validNewKeys)
}
+/**
+ * 全局混入的mounted函数
+ * 内部调用 updateTemplateRef 来更新 setupState (aka. rawBindings)中对元素/组件的引用(ref="xxx")到最新的渲染结果
+ * @param vm 要操作的vm
+ */
export function afterRender(vm: ComponentInstance) {
const stack = [(vm as any)._vnode as VNode]
while (stack.length) {
@@ -185,14 +207,18 @@ export function resolveScopedSlots(
vmStateManager.set(vm, 'slots', slotNames)
}
+// 激活当前实例
export function activateCurrentInstance(
instance: ComponentInternalInstance,
fn: (instance: ComponentInternalInstance) => any,
onError?: (err: Error) => void
) {
+ // 先保存上一个实例
let preVm = getCurrentInstance()
+ // 设置当前实例
setCurrentInstance(instance)
try {
+ // 执行回调setup函数的地方
return fn(instance)
} catch (
// FIXME: remove any
@@ -204,6 +230,7 @@ export function activateCurrentInstance(
throw err
}
} finally {
+ // 恢复上一个实例
setCurrentInstance(preVm)
}
}