vue3中的ref和reactive

第一节:ref和reactive的区别

在 Vue3 中,refreactive 是两种创建响应式数据的核心 API,它们的主要区别体现在数据结构响应式原理使用方式上。以下是详细对比:

1. 数据结构与响应式原理

ref
  • 本质:创建一个包装对象RefImpl),通过 .value 访问内部值。
  • 适用场景:基本类型(如 numberstring)、单个值的响应式处理。
  • 响应式原理:通过 Object.defineProperty().value 转换为 getter/setter。
const count = ref(0);
console.log(count.value); // 0
count.value = 1; // 修改值时触发响应式更新
reactive
  • 本质:创建一个深层响应式对象,对对象的所有嵌套属性都进行代理。
  • 适用场景:复杂对象、数组等引用类型的响应式处理。
  • 响应式原理:基于 ES6 Proxy 实现,拦截对象的属性访问和修改。
const state = reactive({
  count: 0,
  user: { name: 'Alice' }
});
console.log(state.count); // 无需 .value
state.count = 1; // 直接修改属性触发响应式更新

2. 语法与使用方式

ref
  • 声明:使用 ref(初始值),类型通过泛型指定。
  • 模板引用:在模板中自动解包,无需 .value
  • 组件中使用
    <script setup lang="ts">
    const count = ref<number>(0);
    const increment = () => {
      count.value++; // 必须使用 .value 修改值
    };
    </script>
    
    <template>
      <button @click="increment">{{ count }}</button> <!-- 模板中无需 .value -->
    </template>
    
reactive
  • 声明:使用 reactive(对象字面量),类型可通过接口或类型注解明确。
  • 模板引用:直接访问属性,无需 .value
  • 组件中使用

  <script setup lang="ts">
  interface State {
    count: number;
    message: string;
  }
  const state = reactive<State>({
    count: 0,
    message: 'Hello'
  });

  const increment = () => {
    state.count++; // 直接修改属性
  };
  </script>

  <template>
    <button @click="increment">{{ state.count }}</button>
  </template>


### 3. **类型系统差异**
#### `ref`
- 类型为 `Ref<T>`,访问值时需通过 `.value`,类型推导依赖泛型:
  ```typescript
  const count = ref(0); // Ref<number>
  const message = ref<string>('hello'); // 显式指定类型
reactive
  • 类型为原始对象的代理类型,保留对象结构:
    const state = reactive({ count: 0 }); // 类型为 { count: number }
    
  • 注意:reactive 无法直接处理基本类型,必须包装在对象中:
    // 错误:无法直接让基本类型响应式
    const num = reactive(1); // 报错
    
    // 正确:包装在对象中
    const state = reactive({ num: 1 });
    

4. 解构与响应式丢失

ref
  • 通过 .value 保持响应式:
    const count = ref(0);
    const { value } = count; // value 不是响应式的
    
reactive
  • 直接解构会导致响应式丢失,需使用 toRefs
    const state = reactive({ count: 0 });
    const { count } = state; // count 不是响应式的
    
    // 使用 toRefs 保持响应式
    const { count } = toRefs(state); // count 是 Ref<number>
    

5. 适用场景对比

场景refreactive
基本类型响应式const count = ref(0)❌ 需包装在对象中
对象/数组响应式const obj = ref({})const obj = reactive({})
避免响应式丢失✅ 直接传递 .value❌ 需使用 toRefs 解构
函数参数传递✅ 可直接传递 Ref 实例❌ 需确保整个对象被代理
与第三方库集成✅ 可通过 .value 暴露原始值❌ Proxy 对象可能导致兼容性问题

6. 选择建议

  • 使用 ref 场景

    • 管理基本类型(如数字、字符串)。
    • 在组合式函数中返回单个响应式值。
    • 需要与 TypeScript 泛型紧密配合时(如表单验证)。
  • 使用 reactive 场景

    • 管理复杂对象或数组。
    • 避免过多 .value 语法,使代码更简洁。
    • 需要深层响应式监听时(如嵌套对象的变化)。

总结

特性refreactive
响应式原理Getter/Setter (Object.defineProperty)Proxy
数据类型任意类型(包装为 Ref 对象)仅对象/数组(需为引用类型)
访问方式模板中自动解包,JS 中需 .value直接访问属性
类型系统Ref<T>原始对象类型的代理
解构响应式保持响应式(.value需使用 toRefs 保持响应式

在实际开发中,两者通常结合使用:用 reactive 管理复杂状态对象,用 ref 处理简单值或作为函数返回值。

第二节:使用原生的javascript实现ref功能

下面我将使用原生JavaScript实现一个类似Vue3中ref的功能。ref的核心是创建一个响应式对象,通过.value属性访问和修改值,并在值变化时触发依赖更新。

实现思路

  1. 使用ES6的Proxy来拦截对象属性的访问和修改
  2. 创建一个包含value属性的对象,并对其进行代理
  3. 实现依赖收集和触发更新的机制
  4. 添加类型定义以支持TypeScript

原生JavaScript实现

// ref 实现
function ref(initialValue) {
  // 创建一个包含value属性的对象
  const refObject = {
    _value: initialValue,
    get value() {
      // 收集依赖
      track(refObject, 'value');
      return this._value;
    },
    set value(newValue) {
      this._value = newValue;
      // 触发更新
      trigger(refObject, 'value');
    }
  };

  // 返回一个Proxy,使其更加灵活
  return new Proxy(refObject, {
    get(target, key) {
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      if (key === 'value') {
        trigger(target, key);
      }
      return true;
    }
  });
}

// 存储依赖关系的WeakMap
// targetMap: WeakMap<target, depsMap>
// depsMap: Map<key, Set<effect>>
const targetMap = new WeakMap();

// 收集依赖
function track(target, key) {
  if (!activeEffect) return;
  
  // 获取目标对象的依赖映射
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取属性的依赖集合
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  // 将当前活跃的副作用添加到依赖集合中
  dep.add(activeEffect);
  // 同时将依赖集合添加到副作用的依赖列表中
  activeEffect.deps.push(dep);
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  // 创建要执行的副作用集合的副本
  const effects = new Set();
  const computedRunners = new Set();
  
  // 添加属性的依赖副作用
  if (depsMap.has(key)) {
    depsMap.get(key).forEach(effect => {
      if (effect.computed) {
        computedRunners.add(effect);
      } else {
        effects.add(effect);
      }
    });
  }
  
  // 先执行计算属性副作用
  computedRunners.forEach(computed => computed());
  // 再执行普通副作用
  effects.forEach(effect => effect());
}

// 当前活跃的副作用
let activeEffect = null;

// 创建副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    try {
      activeEffect = effectFn;
      // 清除之前的依赖
      cleanup(effectFn);
      // 执行副作用函数并获取返回值
      return fn();
    } finally {
      activeEffect = null;
    }
  };
  
  // 存储该副作用的依赖集合列表
  effectFn.deps = [];
  // 标记是否为计算属性
  effectFn.computed = options.computed || false;
  
  // 如果不是计算属性,则立即执行
  if (!options.lazy) {
    effectFn();
  }
  
  return effectFn;
}

// 清除副作用的依赖
function cleanup(effectFn) {
  effectFn.deps.forEach(dep => {
    dep.delete(effectFn);
  });
  effectFn.deps.length = 0;
}

// 实现计算属性
function computed(getter) {
  let value;
  let dirty = true;
  
  // 创建一个计算属性的副作用
  const runner = effect(getter, {
    lazy: true,
    computed: true
  });
  
  return {
    get value() {
      if (dirty) {
        value = runner();
        dirty = false;
      }
      // 收集依赖,以便在依赖变化时重新计算
      track(this, 'value');
      return value;
    },
    set value(newValue) {
      console.warn('Computed value is readonly');
    }
  };
}

// 示例用法
const count = ref(0);

// 创建一个计算属性
const doubleCount = computed(() => count.value * 2);

// 创建副作用监听count变化
effect(() => {
  console.log('Count changed:', count.value);
});

// 修改count值
count.value = 1; // 输出: Count changed: 1
count.value = 2; // 输出: Count changed: 2

// 访问计算属性
console.log(doubleCount.value); // 输出: 4

代码解释

  1. ref函数:创建一个包含value属性的对象,并使用Proxy代理它。当访问或修改value时,会触发tracktrigger函数。

  2. 依赖收集系统:使用WeakMap存储对象及其依赖关系,当读取响应式对象的属性时收集依赖,当修改属性时触发更新。

  3. 副作用函数effect函数创建一个响应式副作用,当依赖的响应式数据变化时会重新执行。

  4. 计算属性computed函数基于effect实现,具有缓存机制,只有在依赖变化时才会重新计算。

这个实现模拟了Vue3中ref的核心功能,包括响应式追踪、依赖收集和计算属性。在实际应用中,Vue3的实现更加复杂和健壮,还包括生命周期集成、错误处理和性能优化等方面的考虑。

原生 JavaScript 实现的 ref 功能与 Vue3 中的 ref 有哪些区别?

原生 JavaScript 实现的 ref 功能与 Vue3 中的 ref 在核心原理上相似,但在实现细节、功能完整性和性能优化方面存在显著差异。以下是主要区别:

1. 底层实现差异

原生实现
  • 依赖收集:使用 WeakMap + Map + Set 手动管理依赖关系。
  • 响应式触发:通过 Object.definePropertyProxy 拦截属性访问。
  • 示例
    const refObject = {
      _value: initialValue,
      get value() {
        track(this, 'value');
        return this._value;
      },
      set value(newValue) {
        this._value = newValue;
        trigger(this, 'value');
      }
    };
    
Vue3 实现
  • 依赖收集:基于 ReactiveEffect 类和全局副作用栈,支持嵌套副作用。
  • 响应式触发:通过 Proxy 实现深层响应式,支持数组和 Map/Set 等特殊对象。
  • 优化
    • 使用 RefImplComputedRefImpl 类实现更高效的内存管理。
    • 针对不同类型(基本类型、对象、数组)做了特殊处理。

2. 功能完整性

特性原生实现Vue3 实现
基础响应式✅ 支持 .value 读写✅ 支持 .value 读写
计算属性✅ 基础实现✅ 完整支持,含缓存和懒计算
浅层响应式(shallowRef)❌ 未实现✅ 支持
只读引用(readonly)❌ 未实现✅ 支持
自定义响应式(customRef)❌ 未实现✅ 支持
与组件生命周期集成❌ 需手动管理✅ 自动绑定和清理副作用
类型系统基础类型支持完整 TypeScript 集成

3. 性能优化

原生实现
  • 依赖收集:每次访问属性时都需遍历依赖列表。
  • 触发更新:直接触发所有依赖,无批量更新机制。
  • 内存管理:依赖关系可能无法被垃圾回收(如 WeakMap 使用不当)。
Vue3 实现
  • 批量更新:使用微任务队列(queueJob)合并多次更新,避免重复渲染。
  • 细粒度依赖:通过 TrackOpTypesTriggerOpTypes 区分不同操作类型。
  • 优化渲染
    • 基于 shouldTrack 条件判断是否收集依赖。
    • 使用 ReactiveFlags 标记特殊对象,避免重复代理。

4. 与 Vue 生态的集成

原生实现
  • 模板集成:需手动绑定 DOM 更新逻辑。
  • 生命周期:无自动清理机制,需手动管理副作用。
  • Vue 特性:不支持 watchprovide/inject 等。
Vue3 实现
  • 模板自动解包:在 <template> 中无需 .value
  • 生命周期钩子:自动绑定 setup() 和组件实例。
  • Vue 特性
    • watchwatchEffect 无缝协作。
    • 支持 toReftoRefs 等工具函数。

5. 边界情况处理

场景原生实现Vue3 实现
循环引用❌ 可能导致栈溢出✅ 通过 targetMap 避免
非响应式对象❌ 直接返回原始值✅ 通过 toRaw() 显式转换
响应式丢失❌ 需手动处理✅ 提供 toRefs() 保持响应式
数组变异方法❌ 需手动实现✅ 自动处理(如 pushsplice

6. 错误处理与调试

原生实现
  • 错误捕获:需手动添加 try...catch
  • 调试工具:无内置支持,需手动打印日志。
Vue3 实现
  • 错误处理
    • 通过 app.config.errorHandler 全局捕获副作用错误。
    • 计算属性错误会缓存并在访问时抛出。
  • 调试工具
    • Vue DevTools 支持响应式数据可视化。
    • onTrackonTrigger 钩子用于调试依赖关系。

总结

维度原生实现Vue3 实现
实现复杂度较低(基础功能)极高(完整生态)
性能基础优化高度优化(批量更新、缓存等)
功能完整性核心功能完整特性集
与框架集成深度集成(模板、生命周期等)
调试工具手动调试官方 DevTools 支持

原生实现适合学习响应式原理,但在生产环境中,建议使用 Vue3 官方实现以获得完整功能和性能保障。

除了 Proxy,还有哪些方法可以实现类似 ref 的功能?

在 JavaScript 中,除了使用 Proxy 实现响应式外,还可以通过以下几种方式实现类似 Vue3 ref 的功能。每种方法各有优缺点,下面我将逐一介绍并给出示例代码。

1. Object.defineProperty()

这是 Vue2 中使用的核心响应式机制,通过 getter/setter 拦截属性访问。

实现原理

  • 为对象的属性定义 getter 和 setter
  • 在 getter 中收集依赖,在 setter 中触发更新

示例代码

javascript

function ref(initialValue) {
  const refObject = {
    _value: initialValue,
  };

  Object.defineProperty(refObject, 'value', {
    get() {
      track(refObject, 'value');
      return this._value;
    },
    set(newValue) {
      this._value = newValue;
      trigger(refObject, 'value');
    },
  });

  return refObject;
}

// 依赖收集和触发更新的实现与之前相同

优缺点

  • 优点:兼容性好(支持 IE9+)
  • 缺点:
    • 无法检测对象属性的添加或删除
    • 对数组的支持有限(需手动处理变异方法)
    • 只能劫持对象的属性,无法劫持整个对象

2. 发布-订阅模式(Event Emitter)

通过自定义事件系统实现响应式,属性变化时发布事件,依赖方订阅事件。

实现原理

  • 创建一个事件中心,管理事件的发布和订阅
  • ref 对象的 setter 触发事件,getter 可用于依赖收集

示例代码

javascript

class EventEmitter {
  constructor() {
    this.events = new Map();
  }

  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(callback);
  }

  off(event, callback) {
    if (!this.events.has(event)) return;
    const callbacks = this.events.get(event);
    const index = callbacks.indexOf(callback);
    if (index !== -1) callbacks.splice(index, 1);
  }

  emit(event, ...args) {
    if (!this.events.has(event)) return;
    this.events.get(event).forEach(callback => callback(...args));
  }
}

function ref(initialValue) {
  const emitter = new EventEmitter();
  let _value = initialValue;

  return {
    get value() {
      // 收集依赖逻辑
      return _value;
    },
    set value(newValue) {
      _value = newValue;
      emitter.emit('change', newValue);
    },
    onChange(callback) {
      emitter.on('change', callback);
    },
  };
}

// 使用示例
const count = ref(0);
count.onChange((newValue) => {
  console.log('Count changed to:', newValue);
});

count.value = 1; // 输出: Count changed to: 1

优缺点

  • 优点:
    • 实现简单直观
    • 可扩展性强,可自定义各种事件
  • 缺点:
    • 手动管理事件订阅和取消订阅
    • 缺乏自动依赖收集,需显式调用 onChange

3. ES6 类 + 访问器属性

使用 ES6 类的 getter/setter 实现响应式包装。

实现原理

  • 创建一个类,将值存储在私有属性中
  • 通过 public getter/setter 控制值的读写

示例代码

javascript

class Ref {
  constructor(initialValue) {
    this._value = initialValue;
    this.deps = new Set();
  }

  get value() {
    // 收集依赖
    if (activeEffect) {
      this.deps.add(activeEffect);
      activeEffect.deps.push(this.deps);
    }
    return this._value;
  }

  set value(newValue) {
    this._value = newValue;
    // 触发更新
    this.deps.forEach(effect => effect());
  }
}

function ref(initialValue) {
  return new Ref(initialValue);
}

// 副作用函数
let activeEffect = null;
function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn;
    fn();
    activeEffect = null;
  };
  effectFn();
  return effectFn;
}

// 使用示例
const count = ref(0);
effect(() => {
  console.log('Count is:', count.value);
});

count.value = 1; // 输出: Count is: 1

优缺点

  • 优点:
    • 面向对象设计,结构清晰
    • 可通过继承扩展功能
  • 缺点:
    • 无法拦截对象内部属性的变化(只能拦截顶层 value)
    • 相比 Proxy,需要为每个 ref 创建一个类实例

4. RxJS(响应式编程库)

利用 RxJS 的 Observable 实现数据流管理。

实现原理

  • 使用 BehaviorSubject 存储值并发布变更
  • 通过订阅流来响应值的变化

示例代码

javascript

import { BehaviorSubject } from 'rxjs';

function ref(initialValue) {
  const subject = new BehaviorSubject(initialValue);

  return {
    get value() {
      return subject.getValue();
    },
    set value(newValue) {
      subject.next(newValue);
    },
    subscribe(callback) {
      return subject.subscribe(callback);
    }
  };
}

// 使用示例
const count = ref(0);
const subscription = count.subscribe((newValue) => {
  console.log('Count changed to:', newValue);
});

count.value = 1; // 输出: Count changed to: 1
subscription.unsubscribe(); // 取消订阅

优缺点

  • 优点:
    • 强大的异步数据流处理能力
    • 丰富的操作符(map、filter、merge 等)
  • 缺点:
    • 学习曲线陡峭
    • 引入额外的依赖库(RxJS)

5. Symbol + 全局注册表

使用 Symbol 作为唯一标识符,结合全局注册表管理响应式对象。

实现原理

  • 使用 Symbol 作为每个 ref 的唯一标识
  • 通过全局注册表存储 ref 的值和依赖关系

示例代码

javascript

const REF_KEY = Symbol('ref');
const refRegistry = new Map();

function ref(initialValue) {
  const refId = Symbol('refId');
  refRegistry.set(refId, {
    value: initialValue,
    deps: new Set(),
  });

  return {
    get value() {
      track(refId);
      return refRegistry.get(refId).value;
    },
    set value(newValue) {
      refRegistry.get(refId).value = newValue;
      trigger(refId);
    },
  };
}

// 依赖收集和触发更新的实现
function track(refId) {
  if (activeEffect) {
    refRegistry.get(refId).deps.add(activeEffect);
    activeEffect.deps.push(refRegistry.get(refId).deps);
  }
}

function trigger(refId) {
  refRegistry.get(refId).deps.forEach(effect => effect());
}

// 副作用函数
let activeEffect = null;
function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn;
    fn();
    activeEffect = null;
  };
  effectFn();
  return effectFn;
}

优缺点

  • 优点:
    • 避免直接在对象上添加属性,保持对象纯净
    • 更好的内存管理(通过 WeakMap 可实现)
  • 缺点:
    • 全局注册表可能导致命名冲突
    • 调试困难,难以追踪依赖关系

总结对比

方法优点缺点
Proxy深层响应式、拦截能力强兼容性差(IE 不支持)
Object.defineProperty兼容性好只能劫持属性,无法劫持对象
发布-订阅模式实现简单、灵活需手动管理事件订阅
ES6 类面向对象设计、结构清晰只能拦截顶层属性
RxJS强大的异步处理能力学习曲线陡峭、引入额外依赖
Symbol+注册表保持对象纯净、避免属性冲突调试困难、全局状态管理复杂

在实际应用中,Vue3 选择 Proxy 是因为它提供了更强大的元编程能力,可以拦截对象的各种操作,实现深层响应式。而其他方法各有适用场景,例如需要兼容旧浏览器时可考虑 Object.defineProperty,处理复杂异步数据流时 RxJS 是不错的选择。

如何使用原生 JavaScript 实现 reactive 功能?

下面我将使用原生 JavaScript 实现一个类似 Vue3 中 reactive 的功能。reactive 的核心是创建一个深层响应式对象,对对象的所有嵌套属性都进行代理,当属性值变化时触发依赖更新。

实现思路

  1. 使用 ES6 Proxy:拦截对象的属性访问和修改操作

  2. 递归代理嵌套对象:确保深层属性也具有响应式能力

  3. 依赖收集与触发:使用 WeakMap 存储对象与依赖的映射关系

  4. 特殊处理

    • 避免重复代理同一对象
    • 处理数组的变异方法(如 push、splice)
    • 支持不可变对象(readonly)

原生 JavaScript 实现

javascript

// 存储依赖关系的 WeakMap
// targetMap: WeakMap<target, depsMap>
// depsMap: Map<key, Set<effect>>
const targetMap = new WeakMap();

// 当前活跃的副作用函数
let activeEffect = null;

// 创建响应式对象
function reactive(target) {
  // 如果不是对象类型,直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }

  // 如果已经是代理对象,直接返回
  if (target[ReactiveFlags.IS_REACTIVE]) {
    return target;
  }

  // 创建代理
  return createProxy(target);
}

// 创建代理
function createProxy(target) {
  // 检查是否已存在代理
  const existingProxy = getTargetProxy(target);
  if (existingProxy) {
    return existingProxy;
  }

  // 创建新代理
  const proxy = new Proxy(target, {
    get(target, key, receiver) {
      // 处理特殊标志
      if (key === ReactiveFlags.IS_REACTIVE) {
        return true;
      }

      // 获取属性值
      const value = Reflect.get(target, key, receiver);

      // 收集依赖
      track(target, key);

      // 如果值是对象,递归创建响应式代理
      if (typeof value === 'object' && value !== null) {
        return reactive(value);
      }

      return value;
    },
    set(target, key, newValue, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, newValue, receiver);

      // 只有当值真正发生变化时才触发更新
      if (oldValue !== newValue && (oldValue === oldValue || newValue === newValue)) {
        trigger(target, key);
      }

      return result;
    },
    deleteProperty(target, key) {
      const hadKey = key in target;
      const result = Reflect.deleteProperty(target, key);

      // 如果属性存在且被成功删除,触发更新
      if (hadKey && result) {
        trigger(target, key);
      }

      return result;
    }
  });

  // 存储代理与原始对象的映射
  storeTargetProxy(target, proxy);

  return proxy;
}

// 依赖收集
function track(target, key) {
  if (!activeEffect) return;

  // 获取目标对象的依赖映射
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  // 获取属性的依赖集合
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  // 将当前活跃的副作用添加到依赖集合中
  dep.add(activeEffect);
  // 同时将依赖集合添加到副作用的依赖列表中
  activeEffect.deps.push(dep);
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  // 创建要执行的副作用集合的副本
  const effects = new Set();
  
  // 添加属性的依赖副作用
  if (depsMap.has(key)) {
    depsMap.get(key).forEach(effect => {
      effects.add(effect);
    });
  }

  // 执行所有副作用
  effects.forEach(effect => effect());
}

// 创建副作用函数
function effect(fn) {
  const effectFn = () => {
    try {
      // 清除之前的依赖
      cleanup(effectFn);
      
      // 设置当前活跃的副作用
      activeEffect = effectFn;
      
      // 执行副作用函数
      return fn();
    } finally {
      // 重置当前活跃的副作用
      activeEffect = null;
    }
  };
  
  // 存储该副作用的依赖集合列表
  effectFn.deps = [];
  
  // 立即执行副作用以收集依赖
  effectFn();
  
  return effectFn;
}

// 清除副作用的依赖
function cleanup(effectFn) {
  effectFn.deps.forEach(dep => {
    dep.delete(effectFn);
  });
  effectFn.deps.length = 0;
}

// 特殊标志
const ReactiveFlags = {
  IS_REACTIVE: Symbol('is_reactive')
};

// 存储原始对象与代理的映射(简化版)
const proxyMap = new WeakMap();

function getTargetProxy(target) {
  return proxyMap.get(target);
}

function storeTargetProxy(target, proxy) {
  proxyMap.set(target, proxy);
}

// 示例用法
const state = reactive({
  count: 0,
  user: {
    name: 'Alice',
    age: 30
  }
});

// 创建副作用监听 count 变化
effect(() => {
  console.log('Count changed:', state.count);
});

// 修改 count 值
state.count = 1; // 输出: Count changed: 1
state.count = 2; // 输出: Count changed: 2

// 嵌套对象也具有响应式能力
effect(() => {
  console.log('User name changed:', state.user.name);
});

state.user.name = 'Bob'; // 输出: User name changed: Bob

核心功能解析

  1. Proxy 拦截器
    • get:收集依赖并递归代理嵌套对象
    • set:比较新旧值,仅在值变化时触发更新
    • deleteProperty:处理属性删除时的依赖更新
  2. 依赖收集系统
    • 使用 WeakMap<target, Map<key, Set<effect>>> 存储依赖关系
    • 每个属性对应一个依赖集合(Set)
  3. 副作用管理
    • effect 函数创建响应式副作用
    • activeEffect 全局变量跟踪当前执行的副作用
  4. 循环引用处理
    • 使用 proxyMap 避免重复代理同一对象
    • 通过 ReactiveFlags.IS_REACTIVE 标识已代理对象

进阶功能实现(可选)

1. 数组变异方法处理

javascript

// 拦截数组的变异方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  const original = arrayProto[method];
  
  arrayMethods[method] = function(...args) {
    // 先执行原始方法
    const result = original.apply(this, args);
    
    // 通知变更
    const ob = this.__ob__;
    
    // push、unshift、splice 可能会添加新元素,需要对新元素进行响应式处理
    let inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    
    if (inserted) ob.observeArray(inserted);
    
    // 触发更新
    ob.dep.notify();
    return result;
  };
});
2. 计算属性实现

javascript

function computed(getter) {
  let value;
  let dirty = true;
  
  const runner = effect(getter, {
    lazy: true,
    scheduler: () => {
      if (!dirty) {
        dirty = true;
        // 触发依赖于这个计算属性的副作用
        trigger(computedRef, 'value');
      }
    }
  });
  
  const computedRef = {
    [ReactiveFlags.IS_REACTIVE]: true,
    get value() {
      if (dirty) {
        value = runner();
        dirty = false;
      }
      track(computedRef, 'value');
      return value;
    },
    set value() {
      console.warn('Computed value is readonly');
    }
  };
  
  return computedRef;
}
`### 与 Vue3 reactive 的差异

1. **功能完整性**:
   - 缺少 Vue3 的 `shallowReactive``readonly``toRaw` 等工具函数
   - 未处理特殊对象(如 Map、Set、Date 等)
2. **性能优化**- 没有批量更新机制(Vue3 使用微任务队列合并更新)
   - 未实现细粒度的依赖优化
3. **生态集成**- 没有与 Vue 组件生命周期的集成
   - 缺少 DevTools 支持
这个实现展示了 Vue3 reactive 的核心原理,但在生产环境中建议使用官方实现以获得完整功能和性能保障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值