除了基础的 reactive 函数,Vue3 还提供了一系列工具函数来处理各种边界情况:只读数据、浅层响应式、跳过代理、类型判断等。这些工具函数共同构成了完整的响应式工具箱。
前言:为什么需要这些工具函数?
在实际开发中,我们常常会面临各种需求:
- 需要只读数据(如配置对象):
javascript
const config = readonly({ api: '/api', timeout: 3000 });
config.api = '/new-api'; // 错误!不能修改只读数据
- 性能优化:不需要深层响应式:
javascript
const shallow = shallowReactive({ user: { name: '张三' } });
shallow.user.name = '李四'; // 不会触发更新!
- 跳过不需要代理的对象(如第三方实例):
javascript
const instance = markRaw(new ThirdPartyClass());
const state = reactive({ instance }); // instance 不会被代理
- 类型判断:
javascript
if (isReactive(state)) {
console.log(`${state} 是响应式对象`);
}
- 获取原始对象:
javascript
const raw = toRaw(state); // 获取未被代理的原始对象
这些工具函数让响应式系统更加灵活和强大。
readonly - 只读代理
readonly 的基本概念
readonly 会创建一个只读的响应式代理,任何修改操作都会失败,有以下适用场景:
- 全局配置对象(不允许修改)
- 从props传入的不可变数据
- 状态管理中的常量
- 对外暴露的只读接口
readonly 的基础实现
javascript
const ReactiveFlags = {
IS_REACTIVE: '__v_isReactive',
IS_READONLY: '__v_isReadonly'
};
// 工具函数
function isObject(val) {
return val !== null && typeof val === 'object';
}
function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
function isReadonly(value) {
return !!(value && value[ReactiveFlags.IS_READONLY]);
}
// 只读处理器
const readonlyHandlers = {
get(target, key, receiver) {
// 处理特殊标记
if (key === ReactiveFlags.IS_REACTIVE) {
return false;
}
if (key === ReactiveFlags.IS_READONLY) {
return true;
}
const result = Reflect.get(target, key, receiver);
// 嵌套对象也变成只读
if (isObject(result)) {
return readonly(result);
}
return result;
},
set(target, key, value, receiver) {
return true; // 返回true表示操作"成功",但实际上没有修改
},
deleteProperty(target, key) {
return true; // 返回true表示操作"成功",但实际上没有删除
}
};
// 只读代理函数
function readonly(target) {
if (!isObject(target)) {
return target;
}
if (isReadonly(target)) {
return target;
}
return new Proxy(target, readonlyHandlers);
}
shallowReactive - 浅层响应式
shallowReactive 的概念
shallowReactive 会创建一个只对顶层属性进行响应式处理的代理,嵌套对象不会被代理,有以下适用场景:
- 性能优化:大型嵌套对象,但只需要顶层响应式
- 与第三方库集成(不希望代理库内部对象)
- 明确知道嵌套对象不会变化
shallowReactive 的实现
javascript
const ReactiveFlags = {
IS_REACTIVE: '__v_isReactive',
IS_READONLY: '__v_isReadonly'
};
// 工具函数
function isObject(val) {
return val !== null && typeof val === 'object';
}
function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
function isReadonly(value) {
return !!(value && value[ReactiveFlags.IS_READONLY]);
}
// 只读处理器
const readonlyHandlers = {
get(target, key, receiver) {
// 处理特殊标记
if (key === ReactiveFlags.IS_REACTIVE) {
return false;
}
if (key === ReactiveFlags.IS_READONLY) {
return true;
}
const result = Reflect.get(target, key, receiver);
// 浅层:不代理嵌套对象
return result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
return result;
}
};
// 浅层响应式函数
function shallowReactive(target) {
if (!isObject(target)) {
return target;
}
return new Proxy(target, shallowReactiveHandlers);
}
浅层 vs 深层对比
性能考虑
- 深层响应式:需要递归代理所有嵌套对象,初始化开销大
- 浅层响应式:只代理顶层,初始化快,但嵌套对象变化不触发更新
选择指南
- 大型数据对象 → 浅层响应式
- 嵌套对象需要响应式 → 深层响应式
- 性能敏感 → 浅层响应式
- 与第三方库集成 → 浅层响应式
- 简单数据结构 → 深层响应式
markRaw - 跳过代理
markRaw 的概念
markRaw 会标记一个对象,使其永远不会被响应式代理,有以下适用场景:
- 第三方库的实例(如Three.js对象、地图实例)
- 有循环引用的复杂对象
- 不需要响应式的静态数据
- 性能优化:跳过大型但不需响应的对象
markRaw 的实现
javascript
// 定义原始标记
const RAW_MARK = '__v_skip';
// markRaw 函数
function markRaw(value) {
if (isObject(value)) {
Object.defineProperty(value, RAW_MARK, {
value: true,
enumerable: false,
configurable: true,
writable: false
});
}
return value;
}
// 检查是否应该跳过代理
function shouldSkip(value) {
return !!(value && value[RAW_MARK]);
}
// 修改reactive函数,支持跳过
function reactiveWithSkip(target) {
if (!isObject(target)) {
return target;
}
// 检查是否应该跳过
if (shouldSkip(target)) {
return target; // 直接返回原始对象,不进行代理
}
if (isReactive(target)) {
return target;
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) return true;
if (key === ReactiveFlags.RAW) return target;
const result = Reflect.get(target, key, receiver);
// 懒代理时也要检查skip
if (isObject(result) && !shouldSkip(result)) {
return reactiveWithSkip(result);
}
return result;
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});
return proxy;
}
isReactive 类型判断
isReactive 的实现
javascript
// 完善ReactiveFlags
const ReactiveFlags = {
IS_REACTIVE: '__v_isReactive',
IS_READONLY: '__v_isReadonly',
IS_SHALLOW: '__v_isShallow',
RAW: '__v_raw'
};
// 判断函数
function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
function isReadonly(value) {
return !!(value && value[ReactiveFlags.IS_READONLY]);
}
function isShallow(value) {
return !!(value && value[ReactiveFlags.IS_SHALLOW]);
}
function isProxy(value) {
return isReactive(value) || isReadonly(value);
}
toRaw - 获取原始对象
toRaw 的概念
toRaw 会获取响应式代理背后的原始对象,有以下适用场景:
- 需要绕过响应式系统直接操作原始数据
- 传递给不希望接收代理的第三方库
- 性能敏感操作(避免代理开销)
- 调试和测试
toRaw 注意事项
- 对非代理对象调用toRaw会返回自身
- 修改原始对象不会触发更新
- 谨慎使用,避免破坏响应式
toRaw 的实现
javascript
function toRaw(observed) {
const raw = observed && observed[ReactiveFlags.RAW];
return raw ? toRaw(raw) : observed;
}
完整工具函数集实现
javascript
// 定义所有标记
const ReactiveFlags = {
IS_REACTIVE: '__v_isReactive',
IS_READONLY: '__v_isReadonly',
IS_SHALLOW: '__v_isShallow',
RAW: '__v_raw',
SKIP: '__v_skip'
};
// 工具函数
function isObject(val) {
return val !== null && typeof val === 'object';
}
function isFunction(val) {
return typeof val === 'function';
}
// 类型判断
function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
function isReadonly(value) {
return !!(value && value[ReactiveFlags.IS_READONLY]);
}
function isShallow(value) {
return !!(value && value[ReactiveFlags.IS_SHALLOW]);
}
function isProxy(value) {
return isReactive(value) || isReadonly(value);
}
function isRef(value) {
return !!(value && value.__v_isRef === true);
}
// 原始对象获取
function toRaw(observed) {
const raw = observed && observed[ReactiveFlags.RAW];
return raw ? toRaw(raw) : observed;
}
// 跳过代理
const markRaw = (value) => {
if (isObject(value)) {
Object.defineProperty(value, ReactiveFlags.SKIP, {
value: true,
enumerable: false,
configurable: true,
writable: false
});
}
return value;
};
const shouldSkip = (value) => {
return !!(value && value[ReactiveFlags.SKIP]);
};
// 响应式处理器基类
class BaseReactiveHandler {
constructor(readonly = false, shallow = false) {
this.readonly = readonly;
this.shallow = shallow;
}
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !this.readonly;
}
if (key === ReactiveFlags.IS_READONLY) {
return this.readonly;
}
if (key === ReactiveFlags.IS_SHALLOW) {
return this.shallow;
}
if (key === ReactiveFlags.RAW) {
return target;
}
const result = Reflect.get(target, key, receiver);
// 跳过代理检查
if (shouldSkip(result)) {
return result;
}
// 根据模式处理嵌套对象
if (isObject(result)) {
if (this.shallow) {
return result;
}
return this.readonly ? readonly(result) : reactive(result);
}
return result;
}
set(target, key, value, receiver) {
if (this.readonly) {
return true;
}
return Reflect.set(target, key, value, receiver);
}
deleteProperty(target, key) {
if (this.readonly) {
return true;
}
return Reflect.deleteProperty(target, key);
}
getType() {
if (this.readonly && this.shallow) return 'shallowReadonly';
if (this.readonly) return 'readonly';
if (this.shallow) return 'shallowReactive';
return 'reactive';
}
}
// 缓存Map
const reactiveMap = new WeakMap();
const readonlyMap = new WeakMap();
// 创建代理的通用函数
function createReactiveObject(target, handlers, proxyMap) {
if (!isObject(target)) {
return target;
}
if (target[ReactiveFlags.RAW] && !(proxyMap.has(target))) {
return target;
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
if (shouldSkip(target)) {
return target;
}
const proxy = new Proxy(target, handlers);
proxyMap.set(target, proxy);
return proxy;
}
// 主函数
function reactive(target) {
return createReactiveObject(
target,
new BaseReactiveHandler(false, false),
reactiveMap
);
}
// 只读函数
function readonly(target) {
return createReactiveObject(
target,
new BaseReactiveHandler(true, false),
readonlyMap
);
}
// 浅层函数
function shallowReactive(target) {
return createReactiveObject(
target,
new BaseReactiveHandler(false, true),
reactiveMap
);
}
// 浅层只读函数
function shallowReadonly(target) {
return createReactiveObject(
target,
new BaseReactiveHandler(true, true),
readonlyMap
);
}
使用场景总结
- reactive: 默认选择,需要完整响应式
- readonly: 配置对象、常量、对外暴露的只读数据
- shallowReactive: 大型对象、性能优化、明确不需要深层响应式
- shallowReadonly: 只读的大型对象
- markRaw: 第三方实例、不需要响应的对象
- toRaw: 绕过响应式、传递给第三方库、性能敏感操作
- isReactive: '类型判断、调试
结语
本篇文章主要介绍了 reactive 的工具函数集,包含只读函数、浅层响应函数、跳过代理、类型判断等,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!