手写reactive2.0(依赖收集)

假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!

在上篇文章中,我们手搓了一个丐帮的reactive,其实在对数据响应式处理的时候还需要考虑一些额外的逻辑,考虑到响应式数据不仅仅是查值改值这么简单外,还有一系列副作用函数的收集和触发,今天我们这篇文章会从依赖收集出发优化一下之前的手写reative代码。

优化版reactive

在之前丐帮的手写reactive我们使用Proxy代理对象的各种操作后只是简单地进行了读值该值,其实不然,我们还需要做一件很重要的事情------依赖收集(也称作副作用函数收集)!

何为依赖收集

我们都知道,一方有难八方支援是我们中国人美好的道德品质,也正是处于我们是同一种族的同胞,有着千丝万缕的关联。在这种关系的连接下,当一个函数触发影响到的响应式对象还会进而牵连许多相关的函数,比如computed和watch等等就是副作用函数,所以我们还需要对这些副作用函数进行收集。我们编程的响应式对象中的任意属性发生修改都应该将用到了这个属性的各个函数重新执行一遍。那么在此之前,需要知道哪些属性被用到了,也就是依赖收集,也称为副作用函数收集。

1. effect 函数创建副作用函数

effect函数是Vue3中实现响应式的核心之一,它的作用是创建一个响应式的副作用函数。这个副作用函数可以被当作watchcomputed等功能的基础。让我们看一下effect函数的主要实现:

js 复制代码
export function effect(fn, options={}) {
  const effectFn = () => {
    try {
      activeEffect = effectFn
      return fn()
    } finally {
      activeEffect = null
    }
  }
  if (!options.lazy) {
    effectFn()
  }
  effectFn.scheduler = options.scheduler
  return effectFn
}

在这段代码中,effect函数接收一个函数fn和一些配置项options。它内部创建了一个effectFn函数,通过activeEffect变量来追踪当前的副作用函数。在执行fn函数时,将activeEffect设置为当前的effectFn,从而在track函数中收集依赖。最后,将effectFnscheduler属性设置为传入的调度函数,并返回这个effectFn

2. track 函数收集副作用函数

track函数用于在属性被读取时收集副作用函数,以建立属性与副作用函数之间的关联关系。下面是track函数的实现:

js 复制代码
export function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let deps = depsMap.get(key)
  if (!deps) {
    deps = new Set()
  }
  if (!deps.has(activeEffect) && activeEffect) {
    deps.add(activeEffect)
  }
  depsMap.set(key, deps)
}

track函数首先获取目标对象targettargetMap中的依赖映射depsMap,如果不存在,则创建一个新的映射。然后获取属性key对应的依赖集合deps,如果不存在,则创建一个新的集合。最后,将当前的副作用函数activeEffect加入到依赖集合中,并更新depsMap

当目标对象被读取值的时候我们就需要做副作用函数的收集

js 复制代码
function createGetter() {
  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver);
    track(target, key); // 收集依赖

    return res;
  }
}

这样,当属性被读取时,系统就能够追踪到使用该属性的副作用函数,并建立它们之间的关联关系。

3. trigger 函数触发副作用函数

trigger函数用于触发属性的副作用函数,即在属性被修改时执行相应的副作用函数。下面是trigger函数的主要实现:

js 复制代码
export function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }

  const deps = depsMap.get(key)
  if (!deps) {
    return
  }

  deps.forEach(effectFn => {
    if (effectFn.scheduler) {
      effectFn.scheduler()
    } else {
      effectFn()
    }
  });
}

trigger函数首先获取目标对象targettargetMap中的依赖映射depsMap,如果不存在,则直接返回。然后获取属性key对应的依赖集合deps,如果不存在,则直接返回。最后,遍历依赖集合,执行每个副作用函数,如果存在调度函数,则调用调度函数,否则直接执行副作用函数。

当目标对象有属性修改的时候我们就需要做副作用函数的触发,也就是通知所有使用当前属性的副作用函数进行更新

js 复制代码
function createSetter() {
  return function set(target, key, value, receiver) {
    const res = Reflect.set(target, key, value, receiver);
    trigger(target, key); // 触发依赖

    return res;
  }
}

这样,当属性被修改时,系统就能够找到所有使用该属性的副作用函数,通知它们进行更新操作,从而保持数据的响应性。

效果

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { reactive } from './reactive.js';
    import { effect } from './effect.js'

    const state = reactive({
      name: 'sAnL1ng',
      age: 18
    })

    effect(
      () => {
        console.log(`${state.name}今年${state.age}岁了`);
      },
      { 
        lazy: false
      }
    )
  </script>
</body>
</html>

最后再让我们看一遍效果!

总结

reactive可以将引用类型编程响应式主要依靠于Proxy代理,在对象中的属性被读取的时候需要做副作用函数收集,在对象中的属性被修改的时候需要做副作用函数触发。

假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!

开源Git仓库: gitee.com/cheng-bingw...

更多内容:最近手有点痒,手搓一个reactive

相关推荐
却尘1 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare2 小时前
浅浅看一下设计模式
前端
Lee川2 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix2 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人2 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl2 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空2 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust