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) } }