手写 vue 源码 === ref 实现

发布于:2025-05-09 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

响应式的基本实现

Proxy 与属性访问器

Proxy 的工作原理

属性访问器(Getter/Setter)

为什么解构会丢失响应性

ref 和 toRefs 的解决方案

proxyRefs:自动解包 ref

总结


Vue3 的响应式系统是其核心特性之一,它通过 Proxy 和属性访问器实现了数据的响应式变化。本文将深入探讨非原始值(对象、数组等)的响应式方案,以及为什么解构会导致响应式丢失。

响应式的基本实现

Vue3 的响应式系统主要通过以下几个核心 API 实现:

export function reactive(target: any) {
  return createReactiveObject(target);
}

function createReactiveObject(target: any) {
  // 只对对象进行代理
  if (!isObject(target)) {
    return target;
  }
  //  访问属性 会命中 get 方法
  if (target[ReactiveFlags.IS_REACTIVE]) {
    return target;
  }
  // 如果代理对象已经存在,直接返回
  const existingProxy = reactiveMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  let proxy = new Proxy(target, mutableHandlers);
  reactiveMap.set(target, proxy);
  return proxy;
}

Proxy 与属性访问器

Proxy 的工作原理

Proxy 是 ES6 引入的新特性,它允许我们拦截并自定义对象的基本操作。在 Vue3 中,Proxy 用于拦截对象的属性访问和修改:

export const mutableHandlers: ProxyHandler<any> = {
  get(target: any, key: any, receiver: any) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return true;
    }
    track(target, key);
    let res = Reflect.get(target, key, receiver);
    if (isObject(res)) {
      return reactive(res);
    }
    return res;
  },
  set(target: any, key: any, value: any, receiver: any) {
    let oldValue = target[key];
    let result = Reflect.set(target, key, value, receiver);
    if (oldValue !== value) {
      trigger(target, key, value, oldValue);
    }
    return result;
  },
};

属性访问器(Getter/Setter)

属性访问器是 JavaScript 中的一种特性,允许我们通过

getset  方法来控制对象属性的读取和设置:

let flag1 = {
    _v: false,
    get value() { //收集依赖
        return this._v
    },
    set value(newVal) { //触发依赖
        this._v = newVal
    }
}

为什么解构会丢失响应性

当我们对响应式对象进行解构时,会丢失响应性,例如:

const state = reactive({ count: 0 });
const { count } = state; // count 不再是响应式的

这是因为解构实际上是创建了一个新的变量,它只是复制了原始值,而不是保持对原始响应式对象的引用。解构后的变量不再被 Proxy 代理,因此无法触发 getter/setter。

ref 和 toRefs 的解决方案

为了解决这个问题,Vue3 提供了reftoRefs API:

export function ref(value: any) {
  return createRef(value);
}

class RefImpl {
  public __v_isRef = true;
  public _value: any;
  public dep;
  constructor(public rawValue: any) {
    this._value = toReactive(rawValue);
  }
  get value() {
    trackRefValue(this);
    return this._value;
  }
  set value(newVal) {
    if (newVal !== this._value) {
      this.rawValue = newVal;
      this._value = newVal;
      triggerRefValue(this);
    }
  }
}

toRefs 将响应式对象的每个属性转换为 ref:

export function toRefs(obj) {
  const ret = {};
  for (const key in obj) {
    ret[key] = toRef(obj, key);
  }
  return ret;
}

class ObjectRefImpl {
  public __v_isRef = true;
  constructor(public _object, public _key) {}
  get value() {
    return this._object[this._key];
  }
  set value(newVal) {
    this._object[this._key] = newVal;
  }
}

proxyRefs:自动解包 ref

为了使用更方便,Vue3 还提供了proxyRefsAPI,它可以自动解包 ref:

export function proxyRefs(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      let res = Reflect.get(target, key, receiver);
      return res.__v_isRef ? res.value : res;
    },
    set(target, key, value, receiver) {
      let oldValue = target[key];
      if (oldValue.__v_isRef) {
        oldValue.value = value;
        return true;
      } else {
        return Reflect.set(target, key, value, receiver);
      }
    },
  });
}

这样,我们就可以像这样使用:

let proxy = proxyRefs({ ...toRefs(obj) })
console.log(proxy.name); // 直接访问,无需 .value
proxy.name = "李四"; // 直接设置,无需 .value

总结

Vue3 的响应式系统通过 Proxy 和属性访问器实现了对非原始值的响应式处理。解构会导致响应式丢失是因为解构创建了新的变量,失去了与原始响应式对象的连接。通过reftoRefsproxyRefs 等 API,Vue3 提供了优雅的解决方案,使我们能够在保持响应式的同时,享受更简洁的代码风格。


网站公告

今日签到

点亮在社区的每一天
去签到