Vue3源码reactivity响应式篇之watch实现

概览

在vue3中,响应式数据的变化会触发watchwatchEffect中的参数函数,而二者都是基于reactivity包中的watch方法实现,其实现路径为packages\reactivity\src\watch.ts。这两个watch不是一回事。watchwatchEffect是基于本文的watch实现的,是vue3导出给用户使用的API接口,而本文中的watch是在reactivity中定义的,在整个vue3中,其命名为watch$1,vue3在packages\runtime-core\src\apiWatch.ts中引入了watch$1封装了一个doWatch方法,watchwatchEffect就是在doWatch上进一步封装实现的。

源码解析

watch的作用就是主动监听响应式数据source,当source发生变化时,便会触发cb函数,cb函数的参数就是响应式数据source的新值newValue和旧值oldValue,而第三个参数options就是一些配置项,决定了监听的方式、时机等。

watch

watch的源码实现如下:

let activeWatcher= void 0;

function watch(source, cb, options = {}) {
    // source : 可以是ref、reactive对象、数组或getter函数
    // cb : 回调函数,source发生变化时被调用
    const { immediate, deep, once, scheduler, augmentJob, call } = options;
    /* immediate:是否立即执行回调
       deep:是否深度监听
       once:是否只执行一次
       scheduler:调度函数,用于控制回调的执行时机
       augmentJob:用于增强job函数
       call:可选的调用函数,用于调用getter和cb
    */
    const reactiveGetter = (source2) => {
        if (deep) return source2;
        if (isShallow(source2) || deep === false || deep === 0) {
            return traverse(source2, 1)
        }
        return traverse(source2)
    }

    let effect; // 定义副作用
    let getter; // getter函数
    let cleanup; // 清理函数
    let boundCleanup;
    let forceTrigger = false; // 是否强制触发
    let isMultiSource = false; // 是否包含多个监听对象
    if (isRef(source)) {
        // Ref处理
        getter = () => source.value;
        forceTrigger = isShallow(source); // 若是浅层响应,则强制触发,反之
    } else if (isReactive(source)) {
        // Reactive处理
        getter = () => reactiveGetter(source); // 深度遍历控制
        forceTrigger = true; // 强制触发
    } else if (shared.isArray(source)) {
        // 数组处理:遍历每项,处理各种类型
        isMultiSource = true;
        forceTrigger = source.some((s) => isReactive(s) || isShallow(s));
        getter = () => source.map((s) => {
            if (isRef(s)) {
                return s.value;
            } else if (isReactive(s)) {
                return reactiveGetter(s);
            } else if (shared.isFunction(s)) {
                return call ? call(s, 2) : s()
            }
        })
    } else if (shared.isFunction(source)) {
        // 函数处理
        if (cb) {
            // watch 模式
            getter = call ? () => call(source, 2) : source;
        } else {
            // watchEffect 模式
            getter = () => {
                // 执行上次清理
                if (cleanup) {
                    pauseTracking();
                    try {
                        cleanup()
                    } finally {
                        resetTracking();
                    }
                }
                // 设置当前活跃watcher
                const currentEffect = activeWatcher;
                activeWatcher = effect;
                try {
                    // 传入清理函数
                    return call ? call(source, 3, [boundCleanup]) : source(boundCleanup);
                } finally {
                    activeWatcher = currentEffect;
                }
            }
        }
    } else {
        getter = shared.NOOP;
    }
    
    // 深度监听处理:traverse函数会递归访问所有属性,确保收集完整依赖
    if (cb && deep) {
        const baseGetter = getter;
        const depth = deep == true ? Infinity : deep;
        getter = () => traverse(baseGetter(), depth);
    }
    
    // 获取当前副作用域
    const scope = getCurrentScope();

    // 定义watchHandle函数,用于清理副作用effect
    const watchHandle = () => {
        effect.stop();
        if (scope && scope.active) {
            shared.remove(scope.effects, effect);
        }
    }
    
    // 若是一次性监听
    if (once && cb) {
        // 回调函数cb存在,则对cb进行二次封装
        const _cb = cb;
        cb = (...args) => {
            //在cb执行完后,调用watchHandle清理副作用effect
            _cb(...args);
            watchHandle();
        }
    }

    // 初始化 oldValue, INITIAL_WATCHER_VALUE实际上就是一个空对象
    let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE;
    
    // 核心调度任务 job
    const job = (immediateFirstRun) => {
        // immediateFirstRun 表示是否立即执行

        if (!(effect.flags & 1) || !effect.dirty && !immediateFirstRun) {
            return;
        }
        if (cb) {
            // 执行回调,effect会返回新值
            const newValue = effect.run();
            // 变化检测,若deep或者强制触发为true,或者source的值发生变化,若是多源,只要其中一个值发生了变化,则都满足条件
            if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => shared.hasChanged(v, oldValue[i])) : shared.hasChanged(newValue, oldValue))) {
                // 执行清理
                if (cleanup) {
                    cleanup();
                }
                // 设置当前watcher上下文
                const currentWatcher = activeWatcher;
                activeWatcher = effect;
                try {
                    // 构造回调参数
                    const args = [newValue, oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, boundCleanup]
                    oldValue = newValue;
                    // 执行回调
                    call ? call(cb, 3, args) : (cb(...args))
                } finally {
                    activeWatcher = currentWatcher;
                }
            }
        } else {
            // watcherEffect模式直接运行
            effect.run();
        }
    }
    // 是否增强job 
    if (augmentJob) {
        augmentJob(job)
    }

    // 定义副作用,实例化ReactiveEffect,参数为定义的getter函数,即对source进行参数归一化处理后的方法
    effect = new ReactiveEffect(getter);
    
    // 定义副作用的调度器,若配置项scheduler存在,则对job进行调度;否则调度器就是job
    effect.scheduler = scheduler ? () => scheduler(job, false) : job;

    //注册清理函数:清理函数在cleanupMap中维护
    boundCleanup = (fn) => onWatcherCleanup(fn, false, effect);
    cleanup = effect.onStop = () => {
       const cleanups =cleanupMap.get(effect);
       if(cleanups){
         if(call){
           call(cleanups,4)   
         }else{
           for(const cleanup2 of cleanups) cleanup2()
         }
         cleanupMap.delete(effect)
       }
    }
    
    // 若cb回调函数存在
    if (cb) {
        if (immediate) {
            // 若是立即执行,则调用job方法,该方法会执行cb回调
            job(true);
        } else {
            // 若不是立即执行,则调用effect.run,获取旧值oldValue
            oldValue = effect.run();
        }
    // 若存在调度器配置项    
    } else if (scheduler) {
        // 则执行调度器方法,传入job 
        scheduler(job.bind(null, true), true)
    } else {
        // 否则运行effect.run
        effect.run();
    }

    // 定义 watcherHandle的暂停/恢复/停止方法,暂停和恢复就是对effect状态的一个控制
    watchHandle.pause = effect.pause.bind(effect);
    watchHandle.resume = effect.resume.bind(effect);
    watchHandle.stop = watchHandle;
    // 最后返回watcherHandle
    return watchHandle;
}

vue3中定义了一个全局的activeWatcher,可以调用getCurrentWatcher获取当前激活的watcherwatcher方法内部就是对监听对象source进行了参数归一化的处理,由此得到一个getter函数,然后通过将getter参数传给,ReactiveEffect实例化一个副作用effect,当执行effect.run时就会运行getter,由此完成对响应式的依赖进行收集,当这些响应式数据发生变化时,就会触发在watch方法中定义的effect副作用这个依赖,即会触发effect.trigger方法,进而触发其调度器,而watcher中定义的job就会在该调度器中运行,进而执行回调函数cb.

watcher的流程处理

在这里插入图片描述

辅助方法

watch的辅助方法包括onWatcherCleanuptraverse

onWatcherCleanup

vue3定义了一个全局的cleanupMap,该对象中会存储注册清理函数,该清理函数可以右用户自定义,其实现如下:

const cleanupMap = new WeakMap();
function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) {
  if (owner) {
    let cleanups = cleanupMap.get(owner);
    if (!cleanups) cleanupMap.set(owner, cleanups = []);
    cleanups.push(cleanupFn);
  }
}
traverse

traverse方法就是对响应式对象value进行深度递归遍历。

function traverse(value, depth = Infinity, seen) {
    // 若depth小于1 或者 value不是一个对象,或者value的__v_skip属性为true,则返回value
    if (depth <= 0 || !shared.isObject(value) || value('__v_skip')) {
        return value;
    }
    seen = seen || new Set(); // 定义一个 Set
    if(seen.has(value)){ // 若seen存在value,则返回value
       return value  
    }
    seen.add(value); // 向seen中添加value
    depth--; // 深度自减
    // 对value的各类型进行判断,以及递归处理
    if (isRef(value)) {
        traverse(value.value, depth, seen)
    } else if (shared.isArray(value)) {
        for (let i = 0; i < value.length; i++) {
            traverse(value[i], depth, seen);
        }
    } else if (shared.isSet(value) || shared.isMap(value)) {
        value.forEach((v) => {
            traverse(v, depth, seen)
        })
    } else if (shared.isPlainObject(value)) {
        for (const key in value) {
            traverse(value[key], depth, seen)
        }

        for (const key of Object.getOwnPropertySymbols(value)) {
            if (Object.prototype.propertyIsEnumerable.call(value, key)) {
                traverse(value[key], depth, seen)
            }
        }
    }
    
    // 最后返回value;
    return value
}
<think>嗯,用户之前问了JavaScript的复习重点,现在又问Vue3的复习。可能他正在准备前端相关的面试或者考试,需要系统地复习这些技术点。之前我给的JavaScript复习结构比较清晰,用户可能希望Vue3部分也能有类似的层次结构,方便他逐步学习和查漏补缺。 首先,我要确定用户对Vue3的了解程度。可能是已经学过Vue2,现在需要升级到Vue3,或者直接学习Vue3。考虑到Vue3Vue2的区别挺大的,我需要重点对比两者的不同,比如Composition API和Options API的区别,响应式系统的变化等。 接下来,用户可能需要核心概念的复习,比如响应式原理、组件化、生命周期这些。Vue3中新增了setup语法、Teleport、Suspense等特性,这些都是必须涵盖的点。另外,组合式API的使用频率高,面试中常被问到,需要详细说明。 然后,用户可能关心Vue3的新特性和优化,比如性能提升、Tree-shaking支持,以及TypeScript的集成。这些都是Vue3的重要卖点,应该在复习重点中强调。 还要考虑工程实践,比如Vite的使用,Vue Router和Pinia的状态管理。这些工具和库在实际项目中常用,复习时不能忽略。 高频考点方面,用户可能想知道面试常问的问题,比如响应式原理、Composition API的优点、虚拟DOM优化等。需要整理这些点,并给出示例代码,方便用户理解。 最后,复习建议部分应该包括学习资源和实践建议,比如推荐官方文档、实战项目,以及注意TypeScript的结合使用。用户可能希望有明确的学习路径,避免浪费时间在不重要的内容上。 需要注意的是,用户可能希望回答结构清晰,分点列出,便于记忆和复习。同时,代码示例要准确,符合Vue3的语法规范,特别是组合式API的写法。另外,对比Vue2和Vue3的区别能帮助用户更快过渡,应该包含这部分内容。 总结下来,回答的结构应该包括核心概念、新特性、工程实践、高频考点和复习建议,每个部分再细分具体内容,并附上代码示例和注意事项。这样用户能系统地掌握Vue3的重点,应对面试或实际开发需求。</think>以下是针对Vue 3的**核心复习要点梳理**,按技术分层与高频考点分类,帮助系统性掌握关键知识: --- ### 一、核心概念与基础语法 #### 1. **响应式系统** - **核心原理** - 基于Proxy实现响应式Vue 2使用`Object.defineProperty`) - `ref`与`reactive`的区别: ```javascript const count = ref(0); // 包装基本类型(通过.value访问) const state = reactive({ // 直接包装对象 name: &#39;Vue3&#39; }); ``` - **响应式工具** `toRefs`(解构响应式对象)、`computed`、`watch`、`watchEffect` #### 2. **组合式API(Composition API)** - **`setup`函数** ```javascript setup(props, context) { // 在此定义响应式数据、方法 return { ... } // 暴露给模板 } ``` - **逻辑复用** 使用`useXxx`函数封装可复用逻辑(替代Vue 2的Mixins) 示例: ```javascript // useCounter.js export function useCounter() { const count = ref(0); const increment = () => count.value++; return { count, increment }; } ``` #### 3. **生命周期钩子** - Vue 3生命周期前缀改为`on`: ```javascript import { onMounted, onUpdated } from &#39;vue&#39;; setup() { onMounted(() => { console.log(&#39;组件挂载&#39;) }); } ``` - 与Vue 2对应关系: `created` → `setup`中直接执行代码 `mounted` → `onMounted` --- ### 二、Vue 3新特性与优化 #### 1. **性能提升** - 虚拟DOM优化:静态节点提升(Hoist Static)、补丁标记(Patch Flag) - Tree-shaking支持:按需引入API减少打包体积 #### 2. **新组件与API** - **`<Teleport>`**:将组件渲染到DOM任意位置 ```html <teleport to="body"> <Modal /> <!-- 将弹窗渲染到body下 --> </teleport> ``` - **`<Suspense>`**:异步组件加载状态管理 ```html <Suspense> <template #default><AsyncComponent /></template> <template #fallback>Loading...</template> </Suspense> ``` - **`v-model`升级**:支持多个双向绑定 ```html <ChildComponent v-model:title="pageTitle" v-model:content="pageContent" /> ``` #### 3. **TypeScript支持** - 源码使用TypeScript重写 - 类型推断优化:组件Props、Emit类型声明更直观 ```typescript interface Props { msg: string; } const props = defineProps<Props>(); ``` --- ### 三、工程实践与高频考点 #### 1. **Vite项目配置** - 开发服务器基于ESM原生支持,热更新极速 - 配置文件`vite.config.js`常用配置: ```javascript export default defineConfig({ plugins: [vue()], resolve: { alias: { &#39;@&#39;: path.resolve(__dirname, &#39;src&#39;) } } }); ``` #### 2. **状态管理(Pinia)** - 替代Vuex的轻量级方案 - 核心概念:`defineStore`定义Store ```javascript export const useCounterStore = defineStore(&#39;counter&#39;, { state: () => ({ count: 0 }), actions: { increment() { this.count++ } } }); ``` #### 3. **Vue Router 4** - 路由定义变化: ```javascript import { createRouter, createWebHistory } from &#39;vue-router&#39;; const router = createRouter({ history: createWebHistory(), routes: [ { path: &#39;/&#39;, component: Home } ] }); ``` - 组合式API用法: ```javascript import { useRoute, useRouter } from &#39;vue-router&#39;; const route = useRoute(); // 当前路由信息 const router = useRouter(); // 路由跳转方法 ``` --- ### 四、高频面试问题与技巧 1. **Vue 3 vs Vue 2核心区别** - 响应式原理(Proxy vs Object.defineProperty) - Composition API vs Options API - 性能优化策略(如Fragment、静态提升) 2. **响应式原理深入** - 如何手动实现简易Proxy响应式系统? - `ref`为什么需要.value访问? 3. **虚拟DOM Diff算法优化** - 静态节点标记、动态属性标记 --- ### 五、复习建议 1. **优先级排序** Composition API > 响应式原理 > 新特性(Teleport/Suspense) > 性能优化 > 生态工具(Pinia/Vue Router) 2. **实战验证** - 使用Vite搭建项目,体验组合式API - 手写简易响应式系统(Proxy实现) - 对比Vue 2与Vue 3组件写法差异 3. **学习资源** - 官方文档:[Vue 3官方文档](https://vuejs.org/) - 源码分析:GitHub Vue 3源码核心模块(如reactivity) - 实战项目:TodoList、电商后台管理系统 --- **注意**:复习时重点关注组合式API的灵活性与TypeScript深度集成,同时理解底层原理(如Proxy响应式)是面试加分项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值