Vue3.0源码系列(五):响应式原理(shallowReadonly,isProxy)

663 阅读4分钟

大家好,这节为大家带来另外两个响应式的基础API的源码分析和实现,分别是shallowReadonly,isProxy。相信大家在项目中已经对这两个api的应用轻车熟路,在此基础上,探究深层次的原理实现就变的顺理成章,只有这样,才能让我们的能力得到提高。废话不多说,源码分析和讲解正在赶来...... 本人github地址:github.com/zzq921/my-m…

上一篇Vue3.0源码系列(四):响应式原理(readonly,isReadonly,isReactive)

一:shallowReadonly 官网含义:创建一个 proxy,使其自身的 property (属性)为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

首先进行shallowReadonly的单元测试,把其要实现的功能点用单元测试表现出来,通过单元测试来写我们源码逻辑,最后使单元测试通过,最后再优化代码。

单元测试(1):通过shallowReadonly包裹的数据props,我们认为它一定是isReadonly只读的。

单元测试(2):通过shallowReadonly包裹的数据props,深层次的属性不是只读属性。

单元测试(3):当我们试图去对shallowReadonly包裹的数据进行赋值,因为是只读属性,所以会报警告。

//shallowReadonly.spec.ts
import { isReadonly, shallowReadonly } from "../reactive"

describe('shallowReadonly',()=>{
  test('浅响应 readonly',()=>{
    const props = shallowReadonly({
      n:{
        foo:1
      }
    })
    //单元测试(1)
    expect(isReadonly(props)).toBe(true)
    //单元测试(2)
    expect(isReadonly(props.n)).toBe(false)
  })

  it('warn then call set',()=>{
    console.warn = jest.fn()
    const user = shallowReadonly({age:12})
    user.age = 11
    //单元测试(3)
    expect(console.warn).toBeCalled()
  })
})

上节课我们讲解了readonly的底层源码实现,其实非常简单,就是通过readonly的api包裹数据,通过proxy对数据进行监听。因为是只读,素所以不会触发get中的依赖收集track和set重的trigger。shallowReadonly的原理就是在readonly基础上进行扩展,下面是源码的核心实现逻辑。通过源码中的分析,大家有没有觉得对shallowReadonly有了一个深入的了解那。

const readonlyGet = createGetter(true)  //创建readonly的get方法
const shallowReadonlyGet = createGetter(true,true) //创建shallowReadonly的get方法

function createGetter(isReadonly=false,shallow = false) {
  return function get(target,key) {
    if(key === ReactiveFlags.IS_REACTIVE) {
      //isReactive的实现,如果匹配到且不是只读属性,则返回true,即!isReadonly
      return !isReadonly
    }else if(key === ReactiveFlags.IS_READONLY) {
      //isReadonly的实现,如果匹配到是只读属性,则返回
      return isReadonly
    }
    let res = Reflect.get(target,key)
    //判断是否为shallowReadonly,直接返回值就好
    if(shallow) {
      return res
    }
    //数据嵌套判断,如果是isReadonly,就用readonly包裹嵌套的深层数据,如果不是,就用reactive嵌套深层,使其成为响应式数据
    if(isObject(res)) {  
      return isReadonly ? readonly(res) : reactive(res)
    }
    //如果不是readonly,再进行依赖收集
    if(!isReadonly) {
      track(target,key)
    }
    return res
  }
}

二:isProxy 官网定义:检查对象是否是有reactive和readonly创建的proxy

首先进行isProxy的单元测试的书写,我们的单元测试主要在两个方面reactive文件和readonly文件。

单元测试(1):我们希望被reactive包裹的对象是isProxy。

单元测试(2):我们希望被readonly包裹的对象是isProxy。

//reactive.spec.ts
import { isProxy, isReactive, reactive } from '../reactive'
describe('reactive', () => {
  it('happy path',()=>{
    const original = {age:10}
    const observe = reactive(original)
    //单元测试(1)
    expect(isProxy(observe)).toBe(true)
  })
})

//readonly.spec.ts
import { isProxy, isReadonly, readonly } from "../reactive"
describe('readonly',()=>{
  it('happy path',()=>{
    const a = {foo:1,bar:{baz:2}}
    const wrapped = readonly(a)
    //单元测试(2)
    expect(isProxy(wrapped)).toBe(true)
  })
})

有了上面的2个单元测试点,我们就来实现一下这个isProxy的源码,其实通过单元测试,我们就能知道isProxy源码非常简单,就是在我们导出isProxy的方法时候来判断一下,val是否是reactive或者readonly的对象就达到我们的功能了。

export function isProxy(val) {
  return isReactive(val) || isReadonly(val)
}
export function isReactive(value) {
  return !!value[ReactiveFlags.IS_REACTIVE];
}
export function isReadonly(value) {
  return !!value[ReactiveFlags.IS_READONLY];
}

isReactive和isReadonly会执行proxy中的get操作,所以我们又来到了这个createGetter这个方法,所以说这个createGetter是非常重要的,几乎很多重要的响应式api都是在这里实现。

function createGetter(isReadonly=false,shallow = false) {
      return function get(target,key) {
        if(key === ReactiveFlags.IS_REACTIVE) {
          //isReactive的实现,如果匹配到且不是只读属性,则返回true,即!isReadonly
          return !isReadonly
        }else if(key === ReactiveFlags.IS_READONLY) {
          //isReadonly的实现,如果匹配到是只读属性,则返回
          return isReadonly
        }
        let res = Reflect.get(target,key)
        //判断是否为shallowReadonly,直接返回值就好
        if(shallow) {
          return res
        }
        //数据嵌套判断,如果是isReadonly,就用readonly包裹嵌套的深层数据,如果不是,就用reactive嵌套深层,使其成为响应式数据
        if(isObject(res)) {  
          return isReadonly ? readonly(res) : reactive(res)
        }
        //如果不是readonly,再进行依赖收集
        if(!isReadonly) {
          track(target,key)
        }
        return res
      }
}

因为isReactive和isReadonly是上两节课我们讲解的源码知识,这里我们就不过多介绍了,通过这两个的判断,我们就实现了isProxy的功能,就能愉快的在页面中使用isProxy做判断啦,是不是觉得很简单那。

总结:这一篇文章先为大家讲解这两个api的源码实现,相信通过我的讲解,大家会对他们在底层原理实现有一个清晰的认识。欢迎大家来看我的github并且start哈,里面有更详细的分析和理解。

本人github地址:github.com/zzq921/my-m…