Vue.js 3 渐进式实现之响应式系统——第四节:封装 track 和 trigger 函数

往期回顾

  1. 系列开篇与响应式基本实现
  2. effect 函数注册副作用
  3. 建立副作用函数与被操作字段之间的联系

封装 track 和 trigger 函数

目前的实现中,我们直接在 get 拦截函数里编写把副作用函数收集到"桶"里的逻辑,但更好的做法是将这部分逻辑单独封装到一个 track 函数中,取名 track 是为了表达追踪的含义。

同样的,set 中触发副作用函数重新执行的逻辑也可以封装到 trigger 函数中。

代码

javascript 复制代码
// 用一个全局变量存储被注册的副作用函数
let activeEffect
// effect 函数用于注册副作用函数
function effect(fn) {
    // 当调用 effect 注册副作用函数时,将副作用函数 fn 赋值给 activeEffect
    activeEffect = fn
    fn()
}

// 存储副作用函数的桶
const bucket = new WeakMap()

// 原始数据
const data = { text: 'hellow world' }
// 对原始数据的代理
const obj = new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
        // 把副作用函数收集到桶中
        track(traget, key)
        // 返回属性值
        return target[key]
    },
    // 拦截设置操作
    set(target, key newVal) {
        // 设置属性值
        target[key] = newVal
        // 把副作用函数从桶里取出并执行
        trigger(target, key)
        // 返回 true 代表设置操作成功
        return true
    }
})

// track 函数 在 get 拦截函数中被调用,用来追踪副作用函数
function track(target, key) {
    // 没有 activeEffect 直接 return
    if (!activeEffect) return terget[key]

    // 根据 target 从"桶"中取得 depsMap,也是Map类型:key --> effects
    let depsMap = bucket.get(target)
    // 如果不存在 depsMap,就新建一个 Map 并与 target 关联
    if (!depsMap) {
        depsMap = new Map()
        bucket.set(target, depsMap)
    }

    // 再根据 key 从 depsMap 中取得 deps。
    // deps是一个 Set 类型,储存所有与当前 key 相关联的副作用函数
    let deps = depsMap.get(key)
    // 如果 deps 不存在,同样新建一个 Set 并与 key 关联
    if (!deps) {
        deps = new Set()
        depsMap.set(key, deps)
    }

    // 最后将当前激活的副作用函数添加到"桶"里
    deps.add(activeEffect)
}

// trigger 函数 在 set 拦截函数中被调用,用来触发更新
function trigger(target, key) {
    const depsMap = bucket.get(target)
    // 如果这个对象没有被追踪的依赖,没有需要重新运行的副作用函数,直接 return
    if (!depsMap) return

    const effects = depsMap.get(key)
    // 如果这个对象的这个key没有被追踪的依赖,没有需要重新运行的副作用函数,啥也不干
    // 否则就把 effects 中的函数依次执行
    effects && effects.forEach(effect => effect())

    // 使用 ?. 可选链操作符,也可以写成这样
    // bucket.get(target)?.get(key)?.forEach(effect => effect())
}

已实现

本节没有实现任何新功能,只是把依赖收集触发更新 的逻辑分别封装到 tracktrigger 函数中,代码逻辑更清晰并且更加灵活。

负责依赖收集的 track 函数、和负责触发更新的 trigger 函数是响应式系统的核心部分。未来将要实现的很多新功能的逻辑都需要由这两个函数来承载,并且它们也会在不同的地方被调用。因此将 track 和 trigger 单独抽离封装是为了给后续逐步完善响应式系统、增加新功能提供可能。

缺陷/待实现

副作用函数中有不同代码分支比如 if、switch 或三元表达式时,分⽀切换可能会产⽣遗留的副作⽤函数。

下一节我们将详细描述什么是分支切换和遗留的副作用,以及如何解决这个问题。

相关推荐
江城开朗的豌豆10 分钟前
退出登录后头像还在?这个缓存问题坑过多少前端!
前端·javascript·vue.js
江城开朗的豌豆18 分钟前
Vue的'读心术':它怎么知道数据偷偷变了?
前端·javascript·vue.js
江城开朗的豌豆25 分钟前
手把手教你造一个自己的v-model:原来双向绑定这么简单!
前端·javascript·vue.js
我在北京coding38 分钟前
el-tree 懒加载 loadNode
前端·vue.js·elementui
江城开朗的豌豆39 分钟前
v-for中key值的作用:为什么我总被要求加这个'没用的'属性?
前端·javascript·vue.js
goldenocean1 小时前
React之旅-05 List Key
前端·javascript·react.js
Mintopia3 小时前
像素的进化史诗:计算机图形学与屏幕的千年之恋
前端·javascript·计算机图形学
Mintopia3 小时前
Three.js 中三角形到四边形的顶点变换:一场几何的华丽变身
前端·javascript·three.js
归于尽3 小时前
async/await 从入门到精通,解锁异步编程的优雅密码
前端·javascript