Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

  • runtime-core运行时核心模块

    • watch和watcherEffect源码解析
      • 概览
      • 源码解析
        • doWatch
        • 监听方法
        • watch
        • watchEffect
        • watchPostEffect
        • watchSyncEffect
    • scheduler调度器
    • directive自定义指令实现原理
  • 5.18源码学习》
  • runtime-core运行时核心模块
东流
2025-09-09
目录

watch和watcherEffect源码解析

# 概览

在《响应式中的watch实现》中讲解过watch的源码实现,本文主要介绍在runtime-core篇中doWatch的实现,以及watch和watchEffect的区别。具体实现是在packages\runtime-core\src\apiWatch.ts.

# 源码解析

# doWatch

doWatch就是基于reactivity模块中的watch实现的,对参数options进行了定制包装,并且支持在服务端渲染中使用。主要是对scheduler、augmentJob和call进行了包装。

doWatch的源码实现如下:

function doWatch(source, cb, options = {}) {
    // immediate:是否立即执行回调,deep:是否深度监听 flush:调度时机 once:是否只触发一次
    const { immediate, deep, flush, once } = options;
    // 定义基础配置项
    const baseWatchOptions = shared.extend({}, options);

    // 判断是否立即执行。若存在回调函数cb且immediate为true,或者没有回调函数cb且flush值不为post,则也是会立即执行
    const runsImmediately = cb && immediate || !cb && flush !== 'post';
    
    // SSR 处理
    let ssrCleanup;
    // 若是SSR环境,则isInSSRComponentSetup为true
    if (isInSSRComponentSetup) {
        if (flush === 'sync') {
            // 若flush为sync,则调用useSSRContext获取SSR上下文,并且定义上下文的_watcherHandlers属性
            const ctx = useSSRContext();
            ssrCleanup = ctx._watcherHandles || (ctx._watcherHandles = []);
        } else if (!runsImmediately) {
            // 若flush不是sync且不需要立即运行,则返回一个空操作的停止句柄,即不会允许监听
            const watchStopHandle = () => { };
            watchStopHandle.stop = shared.NOOP;
            watchStopHandle.resume = shared.NOOP;
            watchStopHandle.pause = shared.NOOP;
            return watchStopHandle;
        }
    }
    
    // 获取当前组件实例
    const instance = currentInstance;
    // 定义基础配置项的call方法,保证回调会在`callWithAsyncErrorHandling`中执行,可以捕获异步错误
    baseWatchOptions.call = (fn, type, args) => callWithAsyncErrorHandling(fn, instance, type, args);

    // 定义isPre变量,若flush不是post也不是sync,则它的值为true,表示是一个前置观察者
    let isPre = false;
    if (flush === 'post') {
        // 若flush是post,则使用queuePostRenderEffect将回调放入后置队列中,在DOM更新后执行
        baseWatchOptions.scheduler = (job) => {
            queuePostRenderEffect(job, instance && instance.suspense);
        }
    } else if (flush !== 'sync') {
        // 若flush不是post也不是sync,这也是使用watchEffect的默认情况,则调用queueJob将job放入队列中,在组件更新前执行,
        isPre = true;
        baseWatchOptions.scheduler = (job, isFirstRun) => {
            // 第一次运行job时,isFirstRun为true,会立即运行job;后续job会放入队列中运行,即watchEffect(cb);cb第一次会直接运行,后续cb中的响应式数据发生变化时,cb是在队列中运行的
            if (isFirstRun) {
                job();
            } else {
                queueJob(job);
            }
        }
    }

    // 定义基础配置项的augmentJob方法
    baseWatchOptions.augmentJob = (job) => {
        if (cb) {
            // 若存在回调,则标记job的flags的第2位为1
            job.flags |= 4;
        }
        if (isPre) {
            // 若flush不为post,也不是flush,则表示这是一个前置观察者,标记job的flags的第1位为1
            job.flags |= 2;
            // 若存在实例,则关联组件的实例
            if (instance) {
                job.id = instance.id;
                job.i = instance;
            }
        }
    }

    // 定义watchHandle,实际上就是执行reactivity中的watch方法,返回的就是清理方法
    const watchHandle = reactivity.watch(source, cb, baseWatchOptions);
    if (isInSSRComponentSetup) {
        // 在SSR环境中,若存在清理函数,则将watchHandle放入ssrCleanup中
        if (ssrCleanup) {
            ssrCleanup.push(watchHandle)
        } else if (runsImmediately) {
            // 若是立即执行,则立即调用清理函数
            watchHandle();
        }
    }
    
    // 最后返回清理方法watchHandle
    return watchHandle;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

# 监听方法

基于doWatch方法,vue3定义了四种监听方法:watch、watchEffect、watchPostEffect和watchSyncEffect。watchPostEffect和watchSyncEffect就是特殊的watchEffect,指定了flush的值。

# watch

watch方法就是调用doWatch并返回句柄,source为主动监听的对象,可以是ref/reactive响应式对象或者由它们组成的数组或者是一个函数,函数返回的结果是它们。

function watch(source, cb, options) {
    return doWatch(source, cb, options)
}
1
2
3

# watchEffect

watchEffect是被动监听,参数effect是一个回调函数,里面会包含对响应式数据的读取。effect会主动触发一次,后续会在队列中执行。只要effect中的响应式数据发生变化,effect就会被触发执行。默认情况下,即options不传参,watchEffect是一个前置监听,effect会在DOM 更新前触发。

watchEffect源码实现如下:

function watchEffect(effect, options) {
    return doWatch(effect, null, options)
}
1
2
3

watchEffect与watch相比,对于doWatch而言,watchEffect就是对effect进行监听,而没有回调函数cb。

# watchPostEffect

watchPostEffect就是指定了{flush:'post'},在调度器scheduler的处理中,effect会通过queuePostRenderEffect将job放入后置队列中等待执行,即effect会在DOM更新后再执行。

需要注意的是watchPostEffect中的effect没有首次主动执行。

watchPostEffect源码如下:

function watchPostEffect(effect, options) {
    return doWatch(effect, null, { flush: "post" })
}
1
2
3

# watchSyncEffect

watchSyncEffect是指定了{flush:'sync'},表示effect是同步执行,它没有调度器,不会放入队列中去执行,但是会在首次主动执行一次effect。

watchSyncEffect源码如下:

function watchSyncEffect(effect, options) {
    return doWatch(effect, null, { flush: "sync" })
}
1
2
3
编辑 (opens new window)
上次更新: 2025/09/11, 06:22:43
响应式中的watch实现
scheduler调度器

← 响应式中的watch实现 scheduler调度器→

最近更新
01
directive自定义指令实现原理
09-12
02
scheduler调度器
09-10
03
响应式中的watch实现
09-08
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式