如何使用 Proxy 对象实现一个简单的响应式系统?

推荐答案

-- -------------------- ---- -------
-------- ------------- -
  ----- ----- - --- ---------- -
    ----------- ---- -
      ------------- ----- -- ----
      ------ ------------------- -----
    --
    ----------- ---- ------ -
      ----- -------- - ------------------- -----
      ----------- --- ------ -
         ------------------------------
         --------------- ----- -- ----
      -
      ------ ----- -- ------
    -
  ---
  ------ ------
-


--- ------------ - -----
----- --------- - --- ----------

-------- ------------- ---- -
    ----------------- -------
    --- ------- - ----------------------
    -------------
       ------- - --- ------
       --------------------- ---------
    -

    --- ---- - -----------------
    ----------
        ---- - --- ------
        ---------------- ------
    -
    -----------------------
-


-------- --------------- ---- -
    ----- ------- - ----------------------
    ------------ -------
    ----- ---- - -----------------
    --------- -------
    ------------------- -- ----------
-

-------- ---------- -
  ------------ - ---
  ----- -- ----------------
  ------------ - ----- -- -- ------------
-


-- --
----- ---- - ---------- ------ -- ----- ------- ---

--------- -- -
  ------------------ -- -------------- ---- -- ---------------
---

------------- -- ----------- -- -- ---- -- -----
--------- - ------- -- ----------- -- -- ---- -- -----

本题详细解读

核心概念

响应式系统的核心思想是:当数据发生变化时,自动更新视图或其他依赖于这些数据的部分。利用 Proxy 对象,我们可以拦截对对象属性的读取 (get) 和设置 (set) 操作,从而实现依赖收集和触发更新。

实现步骤

  1. reactive(obj) 函数:
    • 使用 Proxy 包装传入的对象 obj
    • get(target, key) 拦截器:
      • 调用 track(target, key) 函数,用于收集依赖。
      • 返回属性值:使用 Reflect.get(target, key) 获取。
    • set(target, key, value) 拦截器:
      • 获取旧值,判断新旧值是否发生改变,没有改变则不更新。
      • 设置属性值:使用 Reflect.set(target, key, value) 设置。
      • 调用 trigger(target, key) 函数,触发更新。
      • 返回 true,表示设置成功。
  2. track(target, key) 函数:
    • 用于依赖收集。
    • 使用 WeakMap (targetMap) 来存储每个对象及其属性的依赖关系。
    • 如果当前没有激活的 effect 函数(activeEffect),则直接返回,不进行依赖收集。
    • targetMap 中存储的 key 为目标对象,valueMap 类型数据,该 Map 的键为属性名,值为该属性的依赖集合(Set)。
    • 如果 targetMap 中不存在当前 target 的记录,则创建新的 Map,并将 targetMap 存储到 targetMap 中。
    • 如果 Map 中不存在当前 key 的记录,则创建新的 Set,并将 keySet 存储到该 Map 中。
    • 将当前激活的 effect 函数(activeEffect) 添加到该属性的依赖集合 Set 中。
  3. trigger(target, key) 函数:
    • 用于触发更新。
    • targetMap 中获取目标对象的所有依赖。
    • 如果不存在目标对象的依赖,则直接返回。
    • 获取目标属性的依赖集合。
    • 如果目标属性不存在依赖,则直接返回。
    • 遍历该属性的依赖集合,并执行每个 effect 函数。
  4. effect(fn) 函数:
    • 接收一个函数 fn 作为参数,该函数表示需要响应式更新的逻辑。
    • fn 赋值给 activeEffect,表示当前正在执行的 effect 函数。
    • 执行 fn(),触发 Proxyget 拦截器,从而进行依赖收集。
    • 执行完成后,清除 activeEffect,防止错误的依赖收集。

示例解析

  1. 创建响应式对象 dataconst data = reactive({ count: 0, text: 'hello' });
  2. 定义一个 effect 函数,输出 datacounttext 属性:
  3. 修改 data.countdata.count++;,触发 Proxyset 拦截器,然后调用 trigger 函数,执行依赖 effect 函数,控制台输出 count is 1, text is hello
  4. 修改 data.textdata.text = 'world',触发 Proxyset 拦截器,然后调用 trigger 函数,执行依赖 effect 函数,控制台输出 count is 1, text is world

优势

  • 简洁性: 使用 Proxy 可以简洁地实现响应式系统,避免了传统的 Object.defineProperty 的繁琐。
  • 拦截所有操作: Proxy 可以拦截 in, delete 等操作,比 Object.defineProperty 更强大。
  • 更好的性能: Proxy 可以拦截数组的操作,从而实现响应式数组,而 Object.defineProperty 需要额外处理数组的变更。
纠错
反馈