大家好,这节为大家带来另外两个响应式的基础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…