reactive 工具函数集

除了基础的 reactive 函数,Vue3 还提供了一系列工具函数来处理各种边界情况:只读数据、浅层响应式、跳过代理、类型判断等。这些工具函数共同构成了完整的响应式工具箱。

前言:为什么需要这些工具函数?

在实际开发中,我们常常会面临各种需求:

  1. 需要只读数据(如配置对象):
javascript 复制代码
const config = readonly({ api: '/api', timeout: 3000 });
config.api = '/new-api'; // 错误!不能修改只读数据
  1. 性能优化:不需要深层响应式:
javascript 复制代码
const shallow = shallowReactive({ user: { name: '张三' } });
shallow.user.name = '李四'; // 不会触发更新!
  1. 跳过不需要代理的对象(如第三方实例):
javascript 复制代码
const instance = markRaw(new ThirdPartyClass());
const state = reactive({ instance }); // instance 不会被代理
  1. 类型判断:
javascript 复制代码
if (isReactive(state)) {
    console.log(`${state} 是响应式对象`);
}
  1. 获取原始对象:
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 的工具函数集,包含只读函数、浅层响应函数、跳过代理、类型判断等,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!

相关推荐
卸任3 小时前
Vibe coding工具使用心得
前端·ai编程
brien94 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance4 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M4 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易4 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding4 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马5 小时前
前沿视界
前端
kyriewen5 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员