reactive和readonly对象嵌套转换,及实现shallowReadonly

前言

官方文档中对reactive的描述:

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

官方文档中对readonly的描述:

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

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

简单的来实践测试一下:

vue 复制代码
<script setup>
import { isReactive, isReadonly, reactive, readonly } from "vue";

const original = reactive({
  nested: {
    foo: 1,
  },
  array: [{ bar: 2 }],
});

const copy = readonly(original);
</script>

<template>
  {{ isReactive(original.nested) }}
  {{ isReactive(original.array) }}
  {{ isReactive(original.array[0]) }}

  {{ isReadonly(copy.nested) }}
</template>

页面中显示四个true

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

reactive

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

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);
});

执行单测,如期不通过。

实现

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

ts 复制代码
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;
  };
}

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

ts 复制代码
export function isObject(val) {
  return val !== null && typeof val === "object";
}

执行单测yarn test reactive

readonly

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

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);
});

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

实现

readonly的实现和reactive一致。

ts 复制代码
if (isObject(res)) {
  return isReadonly ? readonly(res) : reactive(res);
}

执行单测,

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

shallowReadonly

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

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

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

单测

新增shallowReadonly.spec.ts

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

ts 复制代码
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);
});

实现

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

ts 复制代码
export function shallowReadonly(raw) {
  return createActiveObject(raw, shallowReadonlyHandler);
}

baseHandler.ts导出shallowReadonlyHandler对象,

ts 复制代码
export const shallowReadonlyHandler = {}

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

ts 复制代码
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;
  };
}

全局定义:

ts 复制代码
const shallowReadonlyGet = createGetter(true, true);

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

ts 复制代码
export const shallowReadonlyHandler = extend({}, readonlyHandler, {
  get: shallowReadonlyGet,
});

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

ts 复制代码
it("should call console warn when call set", () => {
  console.warn = jest.fn();
  const original = shallowReadonly({ foo: 1 });
  original.foo = 2;

  expect(console.warn).toHaveBeenCalled();
});

执行单测yarn test shallowReadonly

总结

  1. reactive响应式转换是"深层"的:它会影响到所有嵌套的属性。readonly只读代理是深层的,对任何嵌套属性的访问都将是只读的。
  2. 获取对象深层次嵌套,会触发get操作,在判断该嵌套目标也是对象类型时就可以再次用reactivereadonly包裹返回。
  3. shallowReadonly就是在深层次转换和track逻辑之前返回结果即可。
相关推荐
尾善爱看海1 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
weixin_BYSJ19871 小时前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
Bs_MoneyMagnet2 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
驳是2 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea2 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持2 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者2 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn3 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
Bs_MoneyMagnet4 小时前
基于springboot+vue的心理咨询预约与随访平台的设计与实现 源码+文档
vue.js·spring boot·后端·spring·毕业设计·旅游·计算机毕业设计
计算机魔术师5 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端