• Vue3源码reactive和readonly对象嵌套转换,及实现shallowReadonly


    前言

    官方文档中对reactive的描述:

    响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。

    官方文档中对readonly的描述:

    只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

    这意味着嵌套对象内的对象拥有和原对象一样的功能。

    简单的来实践测试一下:

    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    页面中显示四个true

    测试用例可以完善一下,测试之前实现的代码是否支持这样的功能。

    reactive

    reactive.spec.ts中添加测试用例:

    it("nested reactive", () => {
      const original = reactive({
        nested: { foo: 1 },
        array: [{ bar: 2 }],
      });
      expect(isReacive(original.nested)).toBe(true);
      expect(isReacive(original.array)).toBe(true);
      expect(isReacive(original.array[0])).toBe(true);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行单测,如期不通过。

    实现

    实现思路,访问嵌套对象内对象时候,将内部对象也实现响应式,即调用reactive方法。那前提是访问的是对象,则需要添加一个是否是对象的判断。

    import { isObject } from "../shared";
    
    function createGetter(isReadonly = false) {
      return function get(target, key) {
        let res = Reflect.get(target, key);
    
        if (key === ReactiveFlags.IS_REACTIVE) {
          return !isReadonly;
        } else if (key === ReactiveFlags.IS_READONLY) {
          return isReadonly;
        }
    
        if (isObject(res)) {
          return reactive(res);
        }
    
        if (!isReadonly) {
          track(target, key);
        }
        return res;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在公共方法中导出isObject方法,

    export function isObject(val) {
      return val !== null && typeof val === "object";
    }
    
    • 1
    • 2
    • 3

    执行单测yarn test reactive

    readonly

    readonly.spec.ts中完善对嵌套对象的断言,

    it("happy path", () => {
      const original = { foo: 1, bar: { baz: 2 } };
      const wapper = readonly(original);
    
      expect(wapper).not.toBe(original);
      expect(wapper.foo).toBe(1);
    
      expect(isReadonly(wapper)).toBe(true);
      expect(isReadonly(original)).toBe(false);
      // 判断嵌套对象
      expect(isReadonly(wapper.bar)).toBe(true);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行单测yarn test readonly,预期不通过。

    实现

    readonly的实现和reactive一致。

    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    
    • 1
    • 2
    • 3

    执行单测,

    最后再执行全部测试用例,验证是否对其他功能存在影响。

    shallowReadonly

    如果你在上文中点进了官方文档的链接,在readonly的详细描述中:

    要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

    readonly()不同,shallowReadonly没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为ref的属性不会被自动解包了。

    单测

    新增shallowReadonly.spec.ts

    shallowReadonly是浅层转换,还是用isReadonly进行检测,最外层的是可读的,深层次的不是。

    it("should not make non-reactive properties reactive", () => {
      const original = reactive({ foo: 1, bar: { baz: 2 } });
    
      const wapper = shallowReadonly(original);
      expect(isReadonly(wapper)).toBe(true);
      expect(isReadonly(wapper.bar)).toBe(false);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实现

    reactive.ts导出shallowReadonly方法,和readonly类似,只是handler参数不同,

    export function shallowReadonly(raw) {
      return createActiveObject(raw, shallowReadonlyHandler);
    }
    
    • 1
    • 2
    • 3

    baseHandler.ts导出shallowReadonlyHandler对象,

    export const shallowReadonlyHandler = {}
    
    • 1

    handlerget操作都是根据createGetter方法生成,那shallowReadnoly的特点是最外层的是可读的,深层次的不是,可以像readonly一样添加一个参数来判断,如果是shallow直接返回结果,也不需要进行深层次的转换和track

    function createGetter(isReadonly = false, shallow = false) {
      return function get(target, key) {
        let res = Reflect.get(target, key);
    
        if (key === ReactiveFlags.IS_REACTIVE) {
          return !isReadonly;
        } else if (key === ReactiveFlags.IS_READONLY) {
          return isReadonly;
        }
    
        if (shallow) {
          return res;
        }
    
        if (isObject(res)) {
          return isReadonly ? readonly(res) : reactive(res);
        }
    
        if (!isReadonly) {
          track(target, key);
        }
        return res;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    全局定义:

    const shallowReadonlyGet = createGetter(true, true);
    
    • 1

    shallowReadonlyHandler对象和readonlyHandler相似,只是get不同,可以复用之前实现的extend方法。

    export const shallowReadonlyHandler = extend({}, readonlyHandler, {
      get: shallowReadonlyGet,
    });
    
    • 1
    • 2
    • 3

    那可以顺便验证shallowReadonly的更新操作的测试用例,

    it("should call console warn when call set", () => {
      console.warn = jest.fn();
      const original = shallowReadonly({ foo: 1 });
      original.foo = 2;
    
      expect(console.warn).toHaveBeenCalled();
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行单测yarn test shallowReadonly

    总结

    1. reactive响应式转换是“深层”的:它会影响到所有嵌套的属性。readonly只读代理是深层的,对任何嵌套属性的访问都将是只读的。
    2. 获取对象深层次嵌套,会触发get操作,在判断该嵌套目标也是对象类型时就可以再次用reactivereadonly包裹返回。
    3. shallowReadonly就是在深层次转换和track逻辑之前返回结果即可。
  • 相关阅读:
    python+vue维修预约上门服务系统django
    Spring解析之finishBeanFactoryInitialization即初始化单例bean
    @HttpMessageConverter注解的基本介绍
    2、《创建您自己的NFT集合并发布一个Web3应用程序来展示它们》启动并运行您的本地环境
    java计算机毕业设计学生选课系统MyBatis+系统+LW文档+源码+调试部署
    保姆教程系列:小白也能看懂的 Linux 挂载磁盘实操
    古代汉语复习资料与练习题(适合王力版教材)
    树莓派4B开发之五安装yoloV5
    Java项目源码SSM农场信息管理系统
    [附源码]Java计算机毕业设计SSM高校国防教育管理系统
  • 原文地址:https://blog.csdn.net/bertZuo/article/details/134404336