vue3源码-二、响应式原理effect的实现
effect实现
定义
effect
方法:
export function effect(fn, options: any = {}) { // 创建响应式的effect const effect = createReactiveEffect(fn, options); // 默认会让effect先执行一次 if (!options.lazy) { effect(); } return effect; } let uid = 0; let activeEffect; // 存储当前的effect const effectStack = []; // 保存effect 保证依赖关系 function createReactiveEffect(fn, options) { // 返回响应式的effect const effect = function reactiveEffect() { // 保证effect没有加入到effectStack中 if (!effectStack.includes(effect)) { try { effectStack.push(effect); activeEffect = effect; return fn(); } finally { effectStack.pop(); activeEffect = effectStack[effectStack.length - 1]; } } }; effect.id = uid++; // 制作一个effect表示,用于区分effect effect._isEffect = true; // 用于表示这个是响应式effect effect.raw = fn; // 保留effect对应的原函数 effect.options = options; // 在effect上保存用户属性 return effect; }
当
effect
函数执行时,进行取值操作,让属性记住对应的effect
函数。
function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { // ... if (!isReadonly) { // effect函数执行时,进行取值操作,让属性记住对应的effect函数 track(target, TrackOpTypes.GET, key); } } }
需要构建
track
方法进行依赖收集
// 可以拿到当前的effect const targetMap = new WeakMap(); export function track(target, type, key) { // 此属性不需要收集,因为没有在effect使用 if (activeEffect === undefined) { return; } let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } if (!dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps.push(dep); } }
trigger
触发更新对新增属性和修改属性做分类,在
setter
里面
function createSetter(shallow = false) { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { // 新增属性 trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // 修改属性 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } return result; } }
触发
effect
找到依次执行,trigger
方法。
// 找属性对应的effect让其执行(数组,对象) export function trigger(target, type, key?, newValue?, oldValue?) { // 如果这个属性没有,收集过依赖,那不需要做任何操作 const depsMap = targetMap.get(target); if (!depsMap) return; // 所有的要执行的effect全部存放到一个新的集合中,最终一起执行 const effects = new Set(); // 去重 const add = (effectsToAdd) => { if (effectsToAdd) { effectsToAdd.forEach((effect) => effects.add(effect)); } }; // 1.看修改的是不是数组的长度,因为修改长度影响比较大 if (key === "length" && isAarray(target)) { // 如果对应的长度有依赖收集需要更新 depsMap.forEach((dep, key) => { // 如果更改的长度小于收集的索引,那么这个索引也需要触发effect重新执行 if (key === "length" || key > newValue) { add(dep); } }); } else { // 可能是对象 // 这里是修改,不能是新增 if (key !== undefined) { add(depsMap.get(key)); // 如果实现新增 } // 如果修改数组中的某一个索引 switch (type) { case TriggerOrTypes.ADD: if (isAarray(target) && isIntegerKey(key)) { add(depsMap.get("length")); } } } effects.forEach((effect: any) => effect()); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!