reactive 工具函数集

4 阅读6分钟

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

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

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

  1. 需要只读数据(如配置对象):
const config = readonly({ api: '/api', timeout: 3000 });
config.api = '/new-api'; // 错误!不能修改只读数据
  1. 性能优化:不需要深层响应式:
const shallow = shallowReactive({ user: { name: '张三' } });
shallow.user.name = '李四'; // 不会触发更新!
  1. 跳过不需要代理的对象(如第三方实例):
const instance = markRaw(new ThirdPartyClass());
const state = reactive({ instance }); // instance 不会被代理
  1. 类型判断:
if (isReactive(state)) {
    console.log(`${state} 是响应式对象`);
}
  1. 获取原始对象:
const raw = toRaw(state); // 获取未被代理的原始对象

这些工具函数让响应式系统更加灵活和强大。

readonly - 只读代理

readonly 的基本概念

readonly 会创建一个只读的响应式代理,任何修改操作都会失败,有以下适用场景:

  • 全局配置对象(不允许修改)
  • 从props传入的不可变数据
  • 状态管理中的常量
  • 对外暴露的只读接口

readonly 的基础实现

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 的实现

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 的实现

// 定义原始标记
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 的实现

// 完善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 的实现

function toRaw(observed) {
    const raw = observed && observed[ReactiveFlags.RAW];
    return raw ? toRaw(raw) : observed;
}

完整工具函数集实现

// 定义所有标记
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 的工具函数集,包含只读函数、浅层响应函数、跳过代理、类型判断等,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!