0
点赞
收藏
分享

微信扫一扫

Vue3响应式系统

1、响应式系统介绍

  1. Proxy 对象实现属性监听

  2. 多层属性嵌套,在访问属性过程中处理下一级属性

  3. 默认监听动态添加的属性

  4. 默认监听属性的删除操作

  5. 默认监听数组索引和 length 属性

  6. 可以作为单独的模块引用

核心方法:
  • reactive / ref / toRefs / computed
  • effect
  • track
  • trigger

2、Proxy对象基本使用

const jiayin = new Proxy(data, {
    // target     目标对象
    // property   被获取的属性名
    // receiver   Proxy或者继承Proxy的对象
    get(target, key, receiver) {
        console.log("get---", key, receiver);
        return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
        console.log("set---", key, value, receiver);
        return Reflect.set(target, key, receiver); // 在严格模式下,若set方法返回false,则会抛出一个 TypeError 异常。
    },
    deleteProperty(target, key) {
        console.log("delete---", target, key);
        return Reflect.deleteProperty(target, key);
    },
});
  1. setdeleteProperty 需要返回布尔类型的值,严格模式下,如果返回 false 的话会出现 Type Error 的异常;

  2. 如果 handler 没有设置任何拦截,那就等同于直接通向原对象 var proxy = new Proxy({}, {})

  3. 关于 Reflect,一开始有疑问,为什么不直接在 Proxy 内获取到值,非要多此一举使用 Reflect。后来我的理解是,Reflect 对象的方法与 proxy 对象的方法一一对应,且 Reflect 对象用起来更好,所以采用,并不是 proxy 的用不了,也不知道这个理解是否正确。

  4. Proxy 和 Reflect 中使用的 receiver

    const obj = {
        name: "kkk",
        get foo() {
            console.log("this-->", this); // 这个this指向receiver
            return this.bar;
        },
    };
    
    const proxy = new Proxy(obj, {
        get(target, key, receiver) {
            if (key === "bar") {
                return "value - bar";
            }
            return Reflect.get(target, key, receiver);
        },
    });
    console.log(proxy.foo); // value - bar
    

3、reactive实现

前置知识:如何判断对象是否有某个属性

(1)点( . )或者方括号( [ ] )

(2)in 运算符

(3)Reflect.has(target, propertyKey)

(4)hasOwnProperty

总结:

最好使用 Object.prototype.hasOwnProperty.call(foo, "bar"), 这样可以保证用的是当前对象原型上的 hasOwnProperty 方法,不会使用自身的了。

vue3中如果使用 Reflect.has(target, key) 会导致问题,就是 delete obj.hasOwnProperty 的时候,尽管target对象没有 hasOwnProperty 这个属性,也会执行

代码实现

  1. 新建一个 reactive 函数,接收参数 data

  2. 几个工具函数:

    • 判断 data 是否为对象 const isObject = value => typeof value === 'object' || value === null
    • 嵌套对象,判断是否需要递归 const convert = target => isObject(target) ? reactive(target) : target
    • 判断对象中是否拥有该属性 const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)
  3. 创建拦截器对象 handler, 设置 get / set / deleteProperty

  4. 返回 Proxy 对象

// 判断是否是对象
const isObject = (value) => typeof value === "object" || value === null;
// 如果对象是嵌套状态,就递归处理
const convert = (target) => (isObject(target) ? reactive(target) : target);
// 判断对象中是否有该属性
// const hasOwnProperty = (target, key) => target.hasOwnProperty(key) // 这个方法并不好
const hasOwn = (target, key) =>
  Object.prototype.hasOwnProperty.call(target, key);

export const reactive = (data) => {
  if (!isObject(data)) return data;

  const handler = {
    get(target, key, receiver) {
      // 收集依赖
      console.log("get--", key);
      const res = Reflect.get(target, key, receiver);
      return convert(res);
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver); // 获取旧值与新值对比
      let res = true; // 定义返回的布尔变量
      if (oldValue !== value) {
        console.log("set--", key);
        res = Reflect.set(target, key, value, receiver);
        // 触发更新
      }
      return res;
    },
    deleteProperty(target, key) {
      const hasKey = hasOwn(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (hasKey && res) {
        // 触发更新
        console.log("delete---", key);
      }
      return res;
    },
  };

  return new Proxy(data, handler);
};

4、ref 实现

前置知识:js 中的 get、set 方法
/*
- obj中的name是数据属性
- get、set后的age属性是访问器属性
访问器属性:当外部js给age赋值时走的时setter函数,当外部js获取age时 走的getter函数,setter和getter是隐藏函数,会取我们写在age后边的函数
*/
let obj = {
    name: 1,
    get age() {
        return 22;
    },
    set age(value) {
        console.log("set");
        obj.name = value;
    },
};

代码实现

  1. 判断传入的是否是 ref 创建的对象,如果是直接返回;

  2. 传入的不是 ref 创建的对象,内部调用 reactive 创建响应式数据;

  3. 最后创建一个有 value 属性的对象,value属性的值为第二步处理后的值;

    // ... 这里的代码接 reactive 节的代码
    export const ref = (raw) => {
      // 1. 判断 raw 是否是 ref 创建的对象,如果是,直接返回
      if(isObject(raw) && raw.__v_isRef) 
          
      // 2. 传入的是普通数据,进行处理
      // 普通对象 -> 被 reactive 处理为响应式对象   
      // 基本数据 -> 直接返回为基本数据
      let reactiveData = convert(raw)
      
      // 3. 创建一个有 value 属性的对象返回
      const r = {
        __v_isRef: true,		// 注意,这个一定要有
        get value() {
          console.log('ref--get-->>>', reactiveData);
          // todo:收集依赖
          return reactiveData
        },
        set value(newValue) {
          console.log('ref--set-->>>', reactiveData, newValue);
          if(newValue !== reactiveData) {
            raw = newValue
            reactiveData = convert(raw)
            // todo:触发更新
          }
        }
      }
      return r
    }
    

5、reactive vs ref

6、toRefs

  1. toRefs 函数接收一个 reactive 返回的响应式对象,也就是 Proxy 对象
  2. 把传入的对象的所有属性转成一个类似 ref 返回的对象,注意对 array 和其他数据类型的处理
  3. 把转换后后的对象挂载到一个新的对象上返回
export const toRefs = (proxy) => {
  // 这一步不好理解,对对象和其他数据类型的处理
  let ret = proxy instanceof Array ? new Array(proxy.length) : {}
  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }
  return ret
}

// 将属性转成类似 ref 返回的对象
const toProxyRef = (proxy, key) => {
  let r = {
    __v_isRef: true,
    get value() {
      // 注意:这里不需要数据劫持和更新,因为本来就是响应式的数据了,不需要劫持
      return proxy[key]
    },
    set value(newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

7、computed

computed内部使用的是effect实现(还未完全明白)

export const computed = (getter) => {
  const result = ref()
  effect(() => (result.value = getter()))
  return result
}
举报

相关推荐

0 条评论