TA的每日心情 | 无聊 3 天前 |
---|
签到天数: 1050 天 连续签到: 1 天 [LV.10]测试总司令
|
3#
楼主 |
发表于 2020-7-16 14:05:24
|
只看该作者
options.immediate
最终会传递给 vue2 中的 Vue.prototype.$watch 中,逻辑很简单,只要是 true 就在实例化 Watcher 后立即执行一遍就完事了;其相关部分的源码如下:
- const watcher = new Watcher(vm, expOrFn, cb, options)
- if (options.immediate) {
- try {
- cb.call(vm, watcher.value)
- } catch (error) {
- handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
- }
- }
复制代码 而 Watcher 的实现中并没有 immediate 的相关逻辑,也就是说,后续的响应式回调还是异步执行
清除
"watch 和 watchEffect 在停止侦听, 清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新时机 和 侦听器调试 等方面行为一致" -- Composition API 文档
创建和运行
- // src/apis/watch.ts
- // 即 watch() 的参数二 `cb` 的参数三(前俩是 newValue、oldValue)
- // 或 watchEffect() 的参数一 `effect` 的唯一参数
- const registerCleanup: InvalidateCbRegistrator = (fn: () => void) => {
- cleanup = () => {
- try {
- fn()
- } catch (error) {
- logError(error, vm, 'onCleanup()')
- }
- }
- }
- // 下文中运行时间点中真正被执行的
- const runCleanup = () => {
- if (cleanup) {
- cleanup()
- cleanup = null
- }
- }
复制代码 watch() 中的清除回调
在 watch 的情况下,cb 回调中的 cleanup 会在两个时间点被调用:
一个是每次 cb 运行之前:
- const applyCb = (n: any, o: any) => {
- // cleanup before running cb again
- runCleanup()
- cb(n, o, registerCleanup)
- }
- // sync 立即执行 cb,或推入异步队列
- let callback = createScheduler(applyCb)
复制代码 二是 watcher 卸载时:
- // src/apis/watch.ts
- function patchWatcherTeardown(watcher: VueWatcher, runCleanup: () => void) {
- const _teardown = watcher.teardown
- watcher.teardown = function (...args) {
- _teardown.apply(watcher, args)
- runCleanup()
- }
- }
复制代码 watchEffect() 中的失效回调
在 watchEffect 的情况下,cb 回调中的 cleanup (这种情况下也称为 onInvalidate,失效回调)同样会在两个时间点被调用:
- // src/apis/watch.ts
- const watcher = createVueWatcher(vm, getter, noopFn, {
- deep: options.deep || false,
- sync: isSync,
- before: runCleanup,
- })
- patchWatcherTeardown(watcher, runCleanup)
复制代码 首先是遍历执行每个 watcher 时, cleanup 被注册为 watcher.before,文档中称为“副作用即将重新执行时”:
- // vue2 中的 flushSchedulerQueue()
- for (index = 0; index < queue.length; index++) {
- watcher = queue[index]
- if (watcher.before) {
- watcher.before()
- }
- ...
- }
复制代码 其次也是 watcher 卸载时,文档中的描述为:“侦听器被停止 (如果在 setup() 或 生命周期钩子函数中使用了 watchEffect, 则在卸载组件时)”。
watchEffect() 中的 options
watchEffect 相当于没有第一个观察对象 source/sources 的 watch 函数
原来的 cb 函数在这里称为 effect,成为了首个参数,而该回调现在只包含 onCleanup 一个参数
相应的第二个参数仍是 options
默认的 options:
- function getWatchEffectOption(options?: Partial<WatchOptions>): WatchOptions {
- return {
- ...{
- immediate: true,
- deep: false,
- flush: 'post',
- },
- ...options,
- }
- }
复制代码 调用 watchEffect() 时和传入的 options 结合:
- export function watchEffect(
- effect: WatchEffect,
- options?: WatchOptionsBase
- ): WatchStopHandle {
- const opts = getWatchEffectOption(options)
- const vm = getWatcherVM()
- return createWatcher(vm, effect, null, opts)
- }
复制代码 实际能有效传入的只有 deep 和 flush:
- // createWatcher
- const flushMode = options.flush
- const isSync = flushMode === 'sync'
- ...
- // effect watch
- if (cb === null) {
- const getter = () => (source as WatchEffect)(registerCleanup)
- const watcher = createVueWatcher(vm, getter, noopFn, {
- deep: options.deep || false,
- sync: isSync,
- before: runCleanup,
- })
- ...
- }
复制代码- function createVueWatcher( vm, getter, callback, options ): VueWatcher {
- const index = vm._watchers.length
- vm.$watch(getter, callback, {
- immediate: options.immediateInvokeCallback,
- deep: options.deep,
- lazy: options.noRun,
- sync: options.sync,
- before: options.before,
- })
- return vm._watchers[index]
- }
复制代码 关于这点也可以参考下文的 2.1 - test 23、test 24
II. Vue 3.x beta
Vue 3.x beta 中 watch/watchEffect 的签名和之前 @vue/composition-api 中一致,在此不再赘述。
对比、结合前文,该部分将主要关注其单元测试的视角差异,并列出其实现方面的一些区别,希望能加深对本文主题的理解。
主要涉及文件为 packages/runtime-core/src/apiWatch.ts 和 packages/runtime-core/__tests__/apiWatch.spec.ts 等。
2.1 部分测试用例
因为函数的用法相比 @vue/composition-api 中并无改变,Vue 3 中相关的单元测试覆盖的功能部分和前文的版本差不多,写法上似乎更偏重于对 ref/reactive/computed 几种响应式类型的考察。
test 4: 'watching single source: computed ref'
用 watch() 观察一个 computed 对象
在 watch() 调用后,立即对原始 ref 目标赋新值
在 nextTick 中,观察到 computed 对象的新旧值变化符合预期
test 6: 'directly watching reactive object (with automatic deep: true)'
用 watch() 观察一个 const src = reactive({ count: 0 }) 对象
在 watch() 调用后,立即赋值 src.count++
在 nextTick 中,能观察到 count 的新值
test 14: 'cleanup registration (effect)'
用 watchEffect(effect: onCleanup: fn => void) => stop 观察一个响应式对象
在 watchEffect() 调用后,其中立即能观察到目标初始值(默认 immediate: true)
此时,对目标赋新值
在 nextTick 中,观察到新值,且 fn 被调用一次(见 1.3 清理 - watcher.before)
此时,手动调用 stop()
fn 立即又被执行一次
test 15: 'cleanup registration (with source)'
用 watch(source, cb: onCleanup: fn => void) => stop 观察一个响应式对象
在 watch() 调用后,立即对目标赋新值
在 nextTick 中,观察到新值,且此时 fn 未被调用 (见 1.2 - test 14 / 1.3 清理 - watch() 中的清除回调)
此时,再次对目标赋新值
在 nextTick 中,观察到新值,且此时 fn 被调用了一次
此时,手动调用 stop()
fn 立即又被执行一次
test 19: 'deep'
在 options 为 { deep: true } 的情况下
即便是如下这样各种类型互相嵌套,也能正确观察
- const state = reactive({
- nested: {
- count: ref(0)
- },
- array: [1, 2, 3],
- map: new Map([['a', 1], ['b', 2]]),
- set: new Set([1, 2, 3])
- })
复制代码 test 23: 'warn immediate option when using effect'
使用 watchEffect() 的情况下,指定 options 为 { immediate: false }
在 vue 3 中,会忽略 immediate 选项,并 warning 提示 watch() "immediate" option is only respected when using the watch(source, callback, options?) signature.
test 24: 'warn and not respect deep option when using effect'
使用 watchEffect() 的情况下,指定 options 为 { deep: true }
在 vue 3 中,会忽略 deep 选项,并 warning 提示 watch() "deep" option is only respected when using the watch(source, callback, options?) signature.
test 25: 'onTrack'
观察目标为 const obj = reactive({ foo: 1, bar: 2 })
使用 watchEffect(),观察行为依次是 obj.foo、'bar' in obj、Object.keys(obj)
options.onTrack 被调用 3 次,每次的参数依次为:
- // 1st
- {
- target: obj,
- type: TrackOpTypes.GET,
- key: 'foo'
- }
- // 2nd
- {
- target: obj,
- type: TrackOpTypes.HAS,
- key: 'bar'
- }
- // 3rd
- {
- target: obj,
- type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY
- }
复制代码
|
|