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

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

前言

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

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

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

单元测试代码

typescript 复制代码
    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 set
        const 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]) 中读取属性类似,但它是通过一个函数执行来操作的。

typescript 复制代码
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是不是object
    if(isObject(res)) {
      // 如果是isReadonly 就递归调用readonly反之递归调用reactive
      return isReadonly ? readonly(res) : reactive(res)
    }

    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}

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

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

2、实现 shallowReadonly 功能

Vue官网描述

单元测试代码

新建一个shallowReadonly.spec.ts文件

typescript 复制代码
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 = 11
    expect(console.warn).toBeCalled()
  })
});

实现代码

原理也是非常简单

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

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

进入baseHandlers.ts文件

typescript 复制代码
// 把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证明是readonly
      return 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代理逻辑即可~

typescript 复制代码
export const shallowReadonly = (raw) => {
    return createActiveObject(raw, shallowReadonlyHandlers)
}

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

3、实现 isProxy 功能

单元测试

reactive.spec.ts

typescript 复制代码
 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

typescript 复制代码
it("happy path", () => {
    // not set
    const 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

typescript 复制代码
export const isProxy = (value) => {
   //只要是其中之一就返回true
   return isReactive(value) || isReadonly(value)
}

通过本章的所有单元测试

相关推荐
余~~185381628009 分钟前
稳定的碰一碰发视频、碰一碰矩阵源码技术开发,支持OEM
开发语言·人工智能·python·音视频
Am心若依旧40944 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 20课题、单元测试
开发语言·青少年编程·单元测试·编程与数学·goweb
大G哥1 小时前
java提高正则处理效率
java·开发语言
VBA63371 小时前
VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL
开发语言
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
小_太_阳1 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
百万蹄蹄向前冲1 小时前
2024不一样的VUE3期末考查
前端·javascript·程序员
向宇it1 小时前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
alikami2 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json