Readonly
Readonly
跟Reactive
的用法和原理基本一致,不同的是reactive
返回的是响应式数据,readonly
返回的是只读数据,这意味着只能够读取,无法改变对应的键值对,所以也就没有了触发依赖的机会,不需要再收集依赖,同样是通过Proxy
实现
TIP
readonly
是深层次的,其内部值为对象时仍然具有只读性
实现
我们直接在reactive
的代码上来进行兼容改造
1. 创建readonly
export const readonlyMap = new WeakMap()
export const readonly = (target) => {
return createReactiveObject(target, readonlyMap, readonlyHandlers)
}
其中createReactiveObject
在上一章 createreactiveobject优化 涉及,readonlyHandlers
便是readonly
的代理拦截操作
2. Get拦截实现
在上一个篇章中Reactive Get拦截,我们已经进行了一次代码风格的优化,在此基础上来完成我们的readonly
拦截功能,以下是原先的createGetter
函数:
// 枚举
export enum ReactiveFlags {
RAW = '_v_raw_',
IS_REACTIVE = '_v_isReactive'
}
export const createGetter = (isReadonly = false, shallow = false) => {
return (target, key, receiver) => {
// 获取原对象
if (key === ReactiveFlags.RAW && reactiveMap.get(target) === receiver) {
return target
}
// 获取是否为reactive对象
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
const result = Reflect.get(target, key, receiver);
track(target, key, 'get')
if (isObject(result)) {
return reactive(result)
}
return result
}
}
我们需要添加IS_READONLY
枚举,然后获取是否为readonly
时返回true
,同时获取RAW
的key
时也要返回原对象
export enum ReactiveFlags {
RAW = '_v_raw_',
IS_REACTIVE = '_v_isReactive',
IS_READONLY = '_v_isReadonly'
}
export const createGetter = (isReadonly = false, shallow = false) => {
return (target, key, receiver) => {
const isExistReactiveMap = () => key === ReactiveFlags.RAW && reactiveMap.get(target) === receiver
const isExistReadonlyMap = () => key === ReactiveFlags.RAW && readonlyMap.get(target) === receiver
if (key === ReactiveFlags.IS_REACTIVE) {
// 判断是否响应式
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 判断是否为只读
return isReadonly
} else if (
isExistReactiveMap() ||
isExistReadonlyMap()
) {
// 获取原对象
return target
}
const result = Reflect.get(target, key, receiver);
track(target, key, 'get')
if (isObject(result)) {
return reactive(result)
}
return result
}
}
在上面的代码中,我们可以看到几个小的优化点:
- 使用
if-else if
,当key
优先符合判断条件时,不会进行接下来其余判断,减少不必要的计算 - 使用
isExistReactiveMap
和isExistReadonlyMap
函数调用来返回原对象,前面被判断条件符合时不会执行后面函数,减少计算
readonly
的数据是只读的,也就是不需要收集依赖,需要判断isReadonly
进行处理:
export enum ReactiveFlags {
RAW = '_v_raw_',
IS_REACTIVE = '_v_isReactive',
IS_READONLY = '_v_isReadonly'
}
export const createGetter = (isReadonly = false, shallow = false) => {
return (target, key, receiver) => {
const isExistReactiveMap = () => key === ReactiveFlags.RAW && reactiveMap.get(target) === receiver
const isExistReadonlyMap = () => key === ReactiveFlags.RAW && readonlyMap.get(target) === receiver
if (key === ReactiveFlags.IS_REACTIVE) {
// 判断是否响应式
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 判断是否为只读
return isReadonly
} else if (
isExistReactiveMap() ||
isExistReadonlyMap()
) {
// 获取原对象
return target
}
const result = Reflect.get(target, key, receiver);
if (!isReadonly) {
track(target, key, 'get')
}
if (isObject(result)) {
return reactive(result)
}
return result
}
}
readonly
是深层次的,所以如果取出来的值为对象,同样也需要进行readonly
处理:
export enum ReactiveFlags {
RAW = '_v_raw_',
IS_REACTIVE = '_v_isReactive',
IS_READONLY = '_v_isReadonly'
}
export const createGetter = (isReadonly = false, shallow = false) => {
return (target, key, receiver) => {
const isExistReactiveMap = () => key === ReactiveFlags.RAW && reactiveMap.get(target) === receiver
const isExistReadonlyMap = () => key === ReactiveFlags.RAW && readonlyMap.get(target) === receiver
if (key === ReactiveFlags.IS_REACTIVE) {
// 判断是否响应式
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 判断是否为只读
return isReadonly
} else if (
isExistReactiveMap() ||
isExistReadonlyMap()
) {
// 获取原对象
return target
}
const result = Reflect.get(target, key, receiver);
if (!isReadonly) {
track(target, key, 'get')
}
if (isObject(result)) {
return isReadonly ? readonly(result) : reactive(result)
}
return result
}
}
3. Set拦截实现
readonly
返回的数据具有只读性,所以无法进行赋值,也无需进行依赖触发,实现起来比较简单:
const readonlySet = (target, key) => {
// readonly的对象不允许赋值
console.warn(`Failed on key ${key}: target is readonly.`, target)
return true
}
由于开发过程中开发人员可能对readonly
进行赋值,所以给了个warn
提示
4. 收尾工作
定义readonlyHandlers
的Proxy拦截操作,readonly
就创建完成了
// readonly
const readonlyGet = createGetter(true)
const readonlySet = (target, key) => {
// readonly的对象不允许赋值
console.warn(`Failed on key ${key}: target is readonly.`, target)
return true
}
export const readonlyHandlers = {
get: readonlyGet,
set: readonlySet
}
单元测试
describe('readonly', () => {
it ('base', () => {
const form = readonly({
count: 1
})
let sum = 0;
let calls = 0
effect(() => {
sum += form.count;
calls++
})
expect(sum).toBe(1)
expect(calls).toBe(1)
form.count += 1
expect(sum).toBe(1)
expect(calls).toBe(1)
}),
it ('deep readonly', () => {
const form = readonly({
inner: {
count: 1
}
})
let sum = 0;
let calls = 0
effect(() => {
sum += form.inner.count;
calls++
})
expect(sum).toBe(1)
expect(calls).toBe(1)
form.inner.count += 1
expect(sum).toBe(1)
expect(calls).toBe(1)
expect(form.inner.count).toBe(1)
})
})