当前位置: 首页 > news >正文

【Vue3源码】第五章 实现 reactive 和 readonly 嵌套对象绑定响应式

【Vue3源码】第五章 实现 reactive 和 readonly 嵌套对象绑定响应式

前言

上一章节我们实现了isReadonlyisReactive两个API。

还记得第一章时说的proxy无法代理嵌套对象形成响应式的问题吗?这一章我们实现 reactive 和 readonly 嵌套对象转换功能,以及shallowReadonly 和isProxy几个简单的API。

1、实现reactive和readonly嵌套对象转换功能

单元测试代码

    test("nested reactive", () => {const original = {nested: {foo: 1},array: [{ bar: 2 }]}const observed = reactive(original)//我们去监测嵌套对象内部是否是响应式对象expect(isReactive(observed.nested)).toBe(true)expect(isReactive(observed.array)).toBe(true)expect(isReactive(observed.array[0])).toBe(true)})it("nested readonly", () => {// not setconst original = { foo: 1, bar: { baz: 2 } };const wrapped = readonly(original)expect(wrapped).not.toBe(original)expect(wrapped.foo).toBe(1)expect(isReadonly(wrapped)).toBe(true)expect(isReadonly(original)).toBe(false)expect(isReadonly(wrapped.bar)).toBe(true)expect(isReadonly(original.bar)).toBe(false)});

那么怎么实现呢?

实现代码

原理很简单,我们触发get捕获器时,判断传入的内容是否是个object,是个object就继续判断是reactive对象还是readonly对象,最后再递归调用一下reactive函数或者readonly函数给他的嵌套对象也绑定代理响应式。

**Reflect.get()**方法与从 对象 (target[propertyKey]) 中读取属性类似,但它是通过一个函数执行来操作的。

export function isObject (val){return val !== null && typeof val === 'object'
}function createGetter(isReadonly = false) {return function get(target, key) {if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly}const res = Reflect.get(target, key) // 新增// 看看res是不是objectif(isObject(res)) {// 如果是isReadonly 就递归调用readonly反之递归调用reactivereturn isReadonly ? readonly(res) : reactive(res)}if (!isReadonly) {track(target, key)}return res}
}

当然看下我们vue3的源码其实还有更多判断,比如symbol还有接下来马上要实现的shallow

在这里插入图片描述

好了reactive和readonly嵌套对象通过所有单元测试!

2、实现 shallowReadonly 功能

Vue官网描述

在这里插入图片描述

单元测试代码

新建一个shallowReadonly.spec.ts文件

import {  isReadonly,isReactive, shallowReadonly, readonly } from "../reactive";describe("shallowReadonly", () => {test("should not make non-reactive properties reactive", () => {const props = shallowReadonly({ n: { foo: 1 } });const readonlyProps = readonly({ n: { foo: 1 } })expect(isReadonly(props)).toBe(true);//readonly深层的对象也是只读的expect(isReadonly(readonlyProps.n)).toBe(true);// shallowReadonly深层对象是可以set的expect(isReadonly(props.n)).toBe(false);// shallowReadonly深层对象不是响应式对象expect(isReactive(props.n)).toBe(false);});it("should call console.warn when set", () => {console.warn = jest.fn()const user = shallowReadonly({age: 10})user.age = 11expect(console.warn).toBeCalled()})
});

实现代码

原理也是非常简单

它和readonly()API唯一的区别就是只作用于浅层,不需要递归调用使嵌套对象readonly

那么我们就可以通过createGetter高阶函数,新增一个参数作为开关返回shallowReadOnly的get捕获器

进入baseHandlers.ts文件

// 把shallow开关打开
const shallowReadOnlyGet = createGetter(true,true)function createGetter(isReadonly = false, shallow = false) {return function get(target, key) {if (key === ReactiveFlags.IS_REACTIVE) {//get捕获器就返回true告诉调用者这是一个reactive对象return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {// 同理返回isReadonly证明是readonlyreturn isReadonly}const res = Reflect.get(target, key)//判断shallow,如果是shallow是true的话,我们直接返回res即可,就不用去递归调用readonly了if (shallow) {return res}if (isObject(res)) {return isReadonly ? readonly(res) : reactive(res)}if (!isReadonly) {track(target, key)}return res}
}// 导出shallowReadonly专属的shallowReadonlyHandlers
// shallowReadonlyHandlers和readonlyHandlers的set捕获器其实功能是一样的,所以只要更换get捕获器即可
export const shallowReadonlyHandlers = extend({},readonlyHandlers,{get:shallowReadOnlyGet
})

进入reactive.ts文件

我们引入shallowReadonlyHandlers去生成一个专属shallowReadonly的Proxy代理逻辑即可~

export const shallowReadonly = (raw) => {return createActiveObject(raw, shallowReadonlyHandlers)
}

shallowReadonly的单元测试也顺利通过~

3、实现 isProxy 功能

在这里插入图片描述

单元测试

reactive.spec.ts

 it("happy path", () => {const originObj = { foo: 1 }const observed = reactive(originObj)expect(observed).not.toBe(originObj)expect(observed.foo).toBe(1)expect(isReactive(observed)).toBe(true)expect(isReactive(originObj)).toBe(false)//新增expect(isProxy(observed)).toBe(true)})

readonly.spec.ts

it("happy path", () => {// not setconst original = { foo: 1, bar: { baz: 2 } };const wrapped = readonly(original)expect(wrapped).not.toBe(original)expect(wrapped.foo).toBe(1)expect(isReadonly(wrapped)).toBe(true)expect(isReadonly(original)).toBe(false)expect(isReadonly(wrapped.bar)).toBe(true)expect(isReadonly(original.bar)).toBe(false)//新增expect(isProxy(wrapped)).toBe(true)});

代码实现

reactive.ts

export const isProxy = (value) => {//只要是其中之一就返回truereturn isReactive(value) || isReadonly(value)
}

通过本章的所有单元测试

相关文章:

  • MySQL深度分页优化
  • YOLOv5改进 | 检测头篇 | 利用DynamicHead增加辅助检测头进行针对性检测(让小目标无所遁形)
  • Redis需要掌握的知识点总结,包括Redis适用场景、持久化、集群、分区、哨兵、缓存穿透
  • Spark原理——逻辑执行图
  • 笨蛋学设计模式结构型模式-外观模式【10】
  • 华为端口安全常用3种方法配置案例
  • ArcGIS Pro 标注牵引线问题
  • 51-JS鼠标,键盘,表单,粘贴板,窗口事件,遍历节点树,DOM操作:创建/添加,删除,替换
  • 『 C++ 』红黑树RBTree详解 ( 万字 )
  • js动态设置关键侦@keyframes
  • 跟着cherno手搓游戏引擎【8】按键和鼠标的KeyCode
  • LINUX基础培训十之服务管理
  • C++ 编程需要什么样的开发环境?
  • UFW防火墙详解
  • 学习JavaEE的日子 day13 封装 static private this 类加载机制
  • [case10]使用RSQL实现端到端的动态查询
  • 【刷算法】从上往下打印二叉树
  • 08.Android之View事件问题
  • CSS实用技巧
  • IOS评论框不贴底(ios12新bug)
  • JavaScript 奇技淫巧
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • spring学习第二天
  • Unix命令
  • vue-loader 源码解析系列之 selector
  • webpack入门学习手记(二)
  • 订阅Forge Viewer所有的事件
  • 反思总结然后整装待发
  • 记一次用 NodeJs 实现模拟登录的思路
  • 聊聊flink的BlobWriter
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ###C语言程序设计-----C语言学习(6)#
  • #stm32整理(一)flash读写
  • #微信小程序:微信小程序常见的配置传值
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (k8s中)docker netty OOM问题记录
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (一)u-boot-nand.bin的下载
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)3D模板阴影原理
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]