探秘 `Proxy` 和 `Reflect`:从 `useReactive` 看它们的魔法 ✨引言 在 JavaScri
引言
在 JavaScript 的世界里,Proxy
和 Reflect
就像一对神奇的工具,它们让你可以以一种几乎超现实的方式操控对象。今天,我们将通过一个自定义 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
)。这里面,Proxy
和 Reflect
发挥了核心作用。
Proxy
和 Reflect
:魔法的双剑合璧 ⚔️
要真正理解 Proxy
和 Reflect
的魔力,我们需要分开讨论它们的作用,然后再看它们如何协同工作。
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
允许我们在读取或设置 name
和 age
属性时,执行自定义的逻辑。我们手动实现了 get
和 set
方法来模仿默认的操作行为。
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
的优势:对比手动调用的方便性 ⚡
-
保持一致性和简洁性
使用
Reflect
,你可以直接调用Reflect.get
和Reflect.set
,这保证了操作的一致性和简洁性。你不需要自己实现所有的默认行为,只需调用这些方法,它们会执行默认的对象操作,减少了出错的可能性。// 使用 Reflect 确保行为一致 const value = Reflect.get(target, 'name'); const success = Reflect.set(target, 'age', 21);
手动实现这些操作时,你需要确保所有默认行为都被正确实现。遗漏或错误的实现可能导致难以调试的问题。例如,在手动
get
方法中,如果忘记处理未定义的属性,可能会引发意外错误。 -
避免错误和重复代码
手动实现默认行为容易出错。例如,处理属性的
get
方法时,如果忘记检查属性是否存在,可能导致错误。使用Reflect.get
和Reflect.set
可以避免这种错误,因为它们已经实现了这些细节。// 手动实现 get 可能出错 if (prop in target) { return target[prop]; } else { return undefined; // 可能忘记处理属性不存在的情况 } // 使用 Reflect.get 不用担心这些细节 return Reflect.get(target, prop);
-
提升代码可读性
通过使用
Reflect
,代码可以更简洁易懂。例如,Reflect.set(target, key, value)
显示地表示设置对象属性的操作,使得代码意图更加明确。这不仅提高了代码的可读性,还减少了维护成本。 -
完整的操作支持
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
则通过 Proxy
和 Reflect
将状态对象变成一个能够自动触发更新的代理对象。
总结 🌟
通过 Proxy
和 Reflect
的结合,我们能够为普通的对象注入动态的响应能力。Proxy
赋予了对象拦截和自定义行为的能力,而 Reflect
确保了这些行为能够正确地执行。一起使用时,它们让我们能够在组件状态管理中发挥无穷的创造力和控制力。下次,当你在写 React
组件时,记得这对魔法组合,它们能让你的状态管理如同魔法般高效和灵活 ✨🪄。
转载自:https://juejin.cn/post/7409467432899952691