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 或三元表达式时,分⽀切换可能会产⽣遗留的副作⽤函数。

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

相关推荐
记得早睡~4 小时前
leetcode51-N皇后
javascript·算法·leetcode·typescript
小小鸭程序员5 小时前
Vue组件化开发深度解析:Element UI与Ant Design Vue对比实践
java·vue.js·spring·ui·elementui
拉不动的猪5 小时前
vue自定义指令的几个注意点
前端·javascript·vue.js
yanyu-yaya6 小时前
react redux的学习,单个reducer
前端·javascript·react.js
陌路物是人非6 小时前
SpringBoot + Netty + Vue + WebSocket实现在线聊天
vue.js·spring boot·websocket·netty
拉不动的猪6 小时前
uniapp与React Native/vue 的简单对比
前端·vue.js·面试
加瓦点灯6 小时前
观察者模式:解耦对象间的依赖关系
开发语言·javascript·观察者模式
z_mazin7 小时前
Chrome开发者工具实战:调试三剑客
前端·javascript·chrome·网络爬虫
sen_shan8 小时前
Vue3+Vite+TypeScript+Element Plus开发-04.静态菜单设计
前端·javascript·typescript·vue3·element·element plus·vue 动态菜单
旧识君8 小时前
移动端1px终极解决方案:Sass混合宏工程化实践
开发语言·前端·javascript·前端框架·less·sass·scss