源码阅读与调试
watch和computed有些相似的地方,但作用却与computed大有不同。watch可以监听响应式数据的变化,从而触发指定的函数;
js
watch(
() => obj.name,
(value, oldValue) => {
console.log('watch 监听被触发')
console.log('oldValue', oldValue)
console.log('value', value)
},
{
immediate: true,
deep: true
}
)
上面的代码接收3个参数:
1.监听的响应式对象;
2.回调函数cd;
3.配置对象:options
1.immediate:watch初始化完成后被立刻触发一次;
2.deep:深度监听;
基础的watch实例
js
<script>
const { reactive, watch } = Vue
// 1. reactive构建了响应式数据
const obj = reactive({
name: '张三'
})
// 2. 执行了watch函数
watch(obj, (value, oldValue) => {
console.log('watch 监听被触发')
console.log('value', JSON.stringify(value))
})
// 3. 两秒之后触发setter行为
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
reactive构建了响应式数据(前面文章详细写过)
执行watch函数
watch方法的源码在runtime-core/src/apiWatch.ts
的路径下;
ts
export function watch(){
return doWatch(source as any, cb, options)
}
此时doWatch方法传入的3个参数如下图所示:
ts
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
// currentInstance为null
const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
// 如果是一个ref,需要去获取.value
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
// source是一个Reactive
getter = () => source
deep = true
}
if (cb && deep) {
const baseGetter = getter
// 暂时不用管
getter = () => traverse(baseGetter())
}
// isMultiSource判断是不是数组,INITIAL_WATCHER_VALUE是一个空对象
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
// job函数非常重要,它是watch函数的核心
// 此时是定义一个job,具体的暂时不用关注
const job: SchedulerJob = () => {}
// 声明了一个调度器
let scheduler: EffectScheduler
if (flush === 'sync') {
// 略
} else {
// 调度器赋值,queuePreFlushCb中传人job
scheduler = () => queuePreFlushCb(job)
}
// 用getter和scheduler生成effect
const effect = new ReactiveEffect(getter, scheduler)
if (cb) {
if (immediate) {
// immediate为true,直接触发job
job()
} else {
// 当前oldValue是Proxy {name: '张三'}
oldValue = effect.run()
}
}
return () => {
// watch会被停止,effect监听被停止
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
}
SetTimeout两秒之后触发setter行为
测试实例执行完watch函数之后,两秒后会触发Setter行为;Setter行为会触发triggerEffects方法;
triggerEffects方法中effects如下图所示,effects是有3个ReactiveEffect,它的fn此时其实返回的是source;scheduler是一个() => traverse(baseGetter())
方法:
triggerEffects方法=>triggerEffect方法=>effect.scheduler方法=>scheduler = () => queuePreFlushCb(job)=>queueCb方法=>queueFlush方法
queuePreFlushCb方法中传入的cb就是之前doWatch方法中定义的job;
queueFlush方法是关键;
ts
function queueFlush() {
// 此时isFlushing和isFlushPending都为false
if (!isFlushing && !isFlushPending) {
isFlushPending = true
// resolvedPromise是Promise.resolve(),flushJobs都变成异步微任务;
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
此时同步任务已经全部完成,剩下的就是异步微任务了,也就是flushJobs方法;
ts
function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
flushPreFlushCbs(seen)
}
flushJobs方法=>flushPreFlushCbs方法=>通过任务队列的形式触发job;job方法的触发,也就是watch方法的触发;
ts
export function flushPreFlushCbs(
seen?: CountMap,
parentJob: SchedulerJob | null = null
) {
// 此时pendingPreFlushCbs有一个job
if (pendingPreFlushCbs.length) {
currentPreFlushParentJob = parentJob
activePreFlushCbs = [...new Set(pendingPreFlushCbs)]
// 此时pendingPreFlushCbs的length置为空
pendingPreFlushCbs.length = 0
for (
preFlushIndex = 0;
preFlushIndex < activePreFlushCbs.length;
preFlushIndex++
) {
// job函数执行
activePreFlushCbs[preFlushIndex]()
}
}
}
接着我们job函数中都做了什么?
ts
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
if (cb) {
// newValue是Proxy {name: '李四'}
const newValue = effect.run()
if () {
// callWithAsyncErrorHandling对fn函数进行了try catch,从而能够统一处理所有的错误
// cb是(value, oldValue) => {}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onCleanup
])
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
SchedulerJob方法=>callWithAsyncErrorHandling方法=>callWithErrorHandling方法; callWithErrorHandling方法就是对fn函数进行了try catch,从而能够统一处理所有的错误;
ts
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
到这里watch的逻辑就处理完了;watch函数被触发,本质上都是job函数被触发了;
总结
整体分为4块:
- watch函数本身;
- reactive的setter;
- flushJobs;
- job;
代码实现watch
初步实现watch
新建packages/packages/runtime-core/src/apiWatch.ts
文件;
ts
import { isReactive, ReactiveEffect } from "@vue/reactivity";
import { queuePreFlushCb } from "./scheduler";
import { EMPTY_OBJ, hasChanged } from "@vue/shared";
export interface WatchOptions<immediate = boolean> {
immediate?: immediate;
deep?: boolean;
}
export function watch(source, cb: any, options?: WatchOptions) {
return doWatch(source as any, cb, options);
}
function doWatch(
source: any,
cb: Function,
{ immediate, deep }: WatchOptions = EMPTY_OBJ
) {
let getter: () => any;
if (isReactive(source)) {
getter = () => source;
deep = true;
} else {
getter = () => {};
}
if (cb && deep) {
// TODO
const baseGetter = getter;
getter = () => baseGetter();
}
let oldValue = {};
const job: Function = () => {
if (cb) {
const newValue = effect.run();
if (deep || hasChanged(newValue, oldValue)) {
cb(newValue, oldValue);
oldValue = newValue;
}
} else {
effect.run();
}
};
let scheduler = () => queuePreFlushCb(job);
const effect = new ReactiveEffect(getter, scheduler);
if (cb) {
if (immediate) {
job();
} else {
oldValue = effect.run();
}
} else {
effect.run();
}
return () => {
effect.stop();
};
}
修改packages/reactivity/src/reactive.ts;
ts
export const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
export function isReactive(r: any) {
return !!(r && r.__v_isReactive === true);
}
function createReactiveObject(){
proxy[ReactiveFlags][IS_REACTIVE] = true
return proxy
}
在shared/src/index.ts中增加:
ts
export const EMPTY_OBJ: { readonly [key: string]: any } = {};
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue);
记得将watch在两个地方export; 创建一个测试实例:
js
const { reactive, watch } = Vue;
// 1. reactive构建了响应式数据
const obj = reactive({
name: "张三",
});
// 2. 执行了watch函数
watch(
obj,
(value, oldValue) => {
console.log("watch 监听被触发");
console.log("value", JSON.stringify(value));
}
);
// 3. 两秒之后触发setter行为
setTimeout(() => {
obj.name = "李四";
}, 2000);
watch啥也没打印出来!why?!!!依赖是没有被收集吗?
【源码学习】watch如何进行依赖收集?
doWatch方法中有一段代码:
ts
function doWatch(){
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
}
export function traverse(value: unknown, seen?: Set<unknown>) {
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
seen = seen || new Set()
if (seen.has(value)) {
return value
}
seen.add(value)
if (isRef(value)) {
traverse(value.value, seen)
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse((value as any)[key], seen)
}
}
return value
}
这个traverse是递归来取value,目的是来做依赖收集;
【框架实现】实现watch的依赖收集
apiWatch.ts文件中:
ts
function doWatch(){
if (cb && deep) {
// 用来依赖收集
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
}
// 递归获取value,触发getter,进行依赖收集
export const traverse = (value: any) => {
if (!isObject(value)) {
return value;
}
for (const key in value) {
traverse((value as Object)[key]);
}
return value;
};
此时log被打印出来了;