likes
comments
collection
share

探秘 `Proxy` 和 `Reflect`:从 `useReactive` 看它们的魔法 ✨引言 在 JavaScri

作者站长头像
站长
· 阅读数 3

引言

在 JavaScript 的世界里,ProxyReflect 就像一对神奇的工具,它们让你可以以一种几乎超现实的方式操控对象。今天,我们将通过一个自定义 Hook—— useReactive来揭示这两个工具如何协同工作,变成开发者手中的魔法杖 🪄。


observer:魔法的核心 🔮

首先,我们的魔法师 observer 函数,是让普通对象具备响应能力的关键。让我们来逐步解密这段代码:

function observer<T extends Record<string, any>>(initialVal: T, cb: () => void): T {
  // 检查是否已经创建过代理
  const existingProxy = proxyMap.get(initialVal);

  // 如果已有代理,则直接返回
  if (existingProxy) {
    return existingProxy;
  }

  // 防止重新代理已经代理过的对象
  if (rawMap.has(initialVal)) {
    return initialVal;
  }

  // 创建一个新的 Proxy 对象
  const proxy = new Proxy<T>(initialVal, {
    // 拦截对对象属性的读取操作
    get(target, key, receiver) {
      // 获取属性值
      const res = Reflect.get(target, key, receiver);

      // 获取属性的描述符
      const descriptor = Reflect.getOwnPropertyDescriptor(target, key);

      // 如果属性不可配置或只读,直接返回值
      if (!descriptor?.configurable && !descriptor?.writable) {
        return res;
      }

      // 递归地代理对象或数组的属性
      return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res;
    },
    // 拦截对对象属性的设置操作
    set(target, key, val) {
      // 设置属性值
      const ret = Reflect.set(target, key, val);
      // 触发更新回调
      cb();
      return ret;
    },
    // 拦截对对象属性的删除操作
    deleteProperty(target, key) {
      // 删除属性
      const ret = Reflect.deleteProperty(target, key);
      // 触发更新回调
      cb();
      return ret;
    },
  });

  // 缓存原对象与代理对象的关系
  proxyMap.set(initialVal, proxy);
  rawMap.set(proxy, initialVal);

  return proxy;
}

observer 函数 的作用是创建一个代理对象,使得对这个对象的任何访问、修改或删除操作都能被捕捉到并触发回调(cb)。这里面,ProxyReflect 发挥了核心作用。


ProxyReflect:魔法的双剑合璧 ⚔️

要真正理解 ProxyReflect 的魔力,我们需要分开讨论它们的作用,然后再看它们如何协同工作。


Proxy:改变对象的操作方式 🛠️

Proxy 是一个强大的变形金刚,能够拦截并自定义对目标对象的各种操作。它允许我们定义自定义的行为,比如对属性的访问、设置和删除。来看一个示例:

const target = { name: '小明', age: 18 };

const handler = {
  get(target, prop, receiver) {
    // 自定义读取属性的行为
    if (prop in target) {
      return target[prop]; // 手动模仿默认的 get 行为
    }
    return undefined; // 如果属性不存在,返回 undefined
  },
  set(target, prop, value, receiver) {
    // 自定义设置属性的行为
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('Age must be a number');
    }
    // 手动实现默认行为
    target[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:小明
proxy.age = 20; // 正常设置
console.log(proxy.age); // 输出:20
// proxy.age = 'abc'; // 抛出错误:Age must be a number

在这个例子中,Proxy 允许我们在读取或设置 nameage 属性时,执行自定义的逻辑。我们手动实现了 getset 方法来模仿默认的操作行为。


Reflect:确保底层操作的一致性 🔍

Reflect 是一个与 Proxy 紧密配合的工具,提供了一组方法来执行对象的默认操作。这使得我们可以在 Proxy 的陷阱中保持操作的一致性,避免意外的副作用。以下是 Reflect 的一些示例:

const target = { message: 'Hello' };

// 使用 Reflect.get 获取属性值
const value = Reflect.get(target, 'message');
console.log(value); // Logs: Hello

// 使用 Reflect.set 设置属性值
Reflect.set(target, 'message', 'Hi');
console.log(target.message); // Logs: Hi

// 使用 Reflect.deleteProperty 删除属性
Reflect.deleteProperty(target, 'message');
console.log(target.message); // Logs: undefined

Reflect 的优势:对比手动调用的方便性 ⚡

  1. 保持一致性和简洁性

    使用 Reflect,你可以直接调用 Reflect.getReflect.set,这保证了操作的一致性和简洁性。你不需要自己实现所有的默认行为,只需调用这些方法,它们会执行默认的对象操作,减少了出错的可能性。

    // 使用 Reflect 确保行为一致
    const value = Reflect.get(target, 'name');
    const success = Reflect.set(target, 'age', 21);
    

    手动实现这些操作时,你需要确保所有默认行为都被正确实现。遗漏或错误的实现可能导致难以调试的问题。例如,在手动 get 方法中,如果忘记处理未定义的属性,可能会引发意外错误。

  2. 避免错误和重复代码

    手动实现默认行为容易出错。例如,处理属性的 get 方法时,如果忘记检查属性是否存在,可能导致错误。使用 Reflect.getReflect.set 可以避免这种错误,因为它们已经实现了这些细节。

    // 手动实现 get 可能出错
    if (prop in target) {
      return target[prop];
    } else {
      return undefined; // 可能忘记处理属性不存在的情况
    }
    
    // 使用 Reflect.get 不用担心这些细节
    return Reflect.get(target, prop);
    
  3. 提升代码可读性

    通过使用 Reflect,代码可以更简洁易懂。例如,Reflect.set(target, key, value) 显示地表示设置对象属性的操作,使得代码意图更加明确。这不仅提高了代码的可读性,还减少了维护成本。

  4. 完整的操作支持

    Reflect 提供了一组全面的方法来处理对象的所有基本操作(如获取、设置、删除属性),使得在 Proxy 的陷阱中能够完成所有必要的操作,而无需手动处理复杂的逻辑。


useReactive:魔法的应用 🧙‍♂️

useReactive Hook 中,observer 函数的魔力得以应用:

function useReactive<S extends Record<string, any>>(initialState: S): S {
  const update = useUpdate(); // 获取更新函数,用于触发组件重新渲染
  const stateRef = useRef<S>(initialState); // 保存初始状态

  const state = useCreation(() => {
    // 使用 observer 创建响应式对象,并传入更新回调
    return observer(stateRef.current, () => {
      update();
    });
  }, []);

  return state; // 返回响应式状态
}

这个 Hook 的作用是创建一个响应式的状态对象,并确保当状态改变时,组件能够重新渲染。useCreation 用于创建和缓存响应式对象,而 observer 则通过 ProxyReflect 将状态对象变成一个能够自动触发更新的代理对象。


总结 🌟

通过 ProxyReflect 的结合,我们能够为普通的对象注入动态的响应能力。Proxy 赋予了对象拦截和自定义行为的能力,而 Reflect 确保了这些行为能够正确地执行。一起使用时,它们让我们能够在组件状态管理中发挥无穷的创造力和控制力。下次,当你在写 React 组件时,记得这对魔法组合,它们能让你的状态管理如同魔法般高效和灵活 ✨🪄。

转载自:https://juejin.cn/post/7409467432899952691
评论
请登录