readonly
readonly 其实也是通过 reactive 来实现的,只是在 reactive 中添加了一个参数,来指定是否是只读代理。
所以只需要处理 reactive 函数就行了,所以可以修改下之前的 reactive 函数。使用一个高阶函数 createReactiveObject 来实现。
readonly 对应的方法也会变化,比如不能修改,删除等操作,所以可以把 handlers 也传到 createReactiveObject 函数中。
// 分别存储普通代理和只读代理export const targetMap = new WeakMap<Target, any>()export const readonlyMap = new WeakMap<Target, any>()
function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>) { // 判断传入的是否是对象 if (!isObject(target)) { console.log('传入的必须是一个对象') return target }
// 判断是否已经被代理过 const proxyMap = isReadonly ? readonlyMap : targetMap const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy }
// 只要读到了__v_isReactive,就返回target // 因为Proxy对象直接拦截了这个属性 // 同样 读到target[ReactiveFlags.RAW]直接返回对象 if (target[ReactiveFlags.RAW] && target[ReactiveFlags.IS_REACTIVE]) { return target }
const proxy = new Proxy(target, baseHandlers)
// 存储代理对象 proxyMap.set(target, proxy)
return proxy}然后修改 reactive 函数,来使用 createReactiveObject 函数
export function reactive<T extends object>(target: T): Texport function reactive(target: object) { // 如果已经是只读代理,直接返回 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target }
// 第二个参数false,表示不是只读,并且传入的是 mutableHandlers return createReactiveObject(target, false, mutableHandlers)}添加 readonly 函数
type DeepReadonly<T extends Record<string, any>> = T extends any ? { readonly [K in keyof T]: T[K] extends Record<string, any> ? DeepReadonly<T[K]> : T[K] } : never
export function readonly<T extends object>(target: T): DeepReadonly<T> { return createReactiveObject( target, true, readonlyHandlers // readonly的handler处理程序需要单独进行处理 )}然后再处理 readonlyHandlers
也可以创建一个 createGetter 函数,通过传入的参数来判断应该创建什么样的 getter 函数。
function createGetter(isReadonly = false) { return function get(target: object, key: string | symbol, receiver: object): any { // 如果进入到get方法,说明肯定是一个proxy代理对象 // 如果访问的是__v_isReactive,返回true if (key === ReactiveFlags.IS_REACTIVE) { return true } else if (key === ReactiveFlags.IS_READONLY) { // 如果访问的是ReactiveFlags.IS_READONLY, 返回true return isReadonly } else if (key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap : targetMap).get(target)) { // 访问的是 __v_raw 属性,并且是代理对象本身在访问 return target }
// 只有在非只读的情况下才会收集依赖 if (!isReadonly) { track(target, TrackOpTypes.GET, key) }
// 判断是不是数组,如果是数组,并且 key 是 arrayInstrumentations 对应的方法 const targetIsArray = isArray(target) if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver) }
// 返回对象的相应属性值 const result = Reflect.get(target, key, receiver)
// 判断是不是对象,是对象就递归代理 // 如果整个对象是只读的,那么这个对象的属性是对象,也应该是只读的 if (isObject(result)) { return isReadonly ? readonly(result) : reactive(result) }
return result }}然后再创建 readonlyHandlers
const get = createGetter()const readonlyGet = createGetter(true)
export const mutableHandlers: ProxyHandler<object> = { get, set, has, ownKeys, deleteProperty,}
export const readonlyHandlers: ProxyHandler<object> = { get: readonlyGet, set(target, key) { console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target) return true }, deleteProperty(target, key) { console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target) return true },}最后测试下
import { reactive, readonly } from '../dist/reactivity.esm.js'
const obj = { a: 1, b: 2, c: { d: 3, },}
const readonlyProxy = readonly(obj)// readonly不触发依赖收集readonlyProxy.a
// 对象的直接属性不能修改readonlyProxy.a = 2console.log(readonlyProxy.a)
// 嵌套的对象的属性也不能修改readonlyProxy.c.d = 22console.log(readonlyProxy.c.d)
最后贴一下修改完的完整代码 effect 和 reactive 文件
import { hasChanged, hasOwn, isArray, isObject } from '@vue/shared'
import { enableTracking, ITERATE_KEY, pauseTracking, track, trigger } from './effect'import { TrackOpTypes, TriggerOpTypes } from './operations'import { reactive, ReactiveFlags, readonly, readonlyMap, targetMap, toRaw } from './reactive'
const arrayInstrumentations: Record<string, Function> = {}
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => { // 获取原生方法的引用 const method = Array.prototype[key] as any
arrayInstrumentations[key] = function (this: unknown[], ...args: unknown[]) { // 将 this 转化为 非响应式(代理)对象 --> 这里的 this 就是调用这些方法的数组 const arr = toRaw(this)
// 遍历当前数组的每个索引,通过track函数对数组索引进行依赖收集 for (let i = 0, l = this.length; i < l; i++) { track(arr, TrackOpTypes.GET, i + '') }
// 直接在原始对象中查找,使用原始数组和参数 const res = method.apply(arr, args)
if (res === -1 || res === false) { // 如果在原始数组中没有找到,注意,还需要进行处理,因为参数也有可能是响应式的 return method.apply(arr, args.map(toRaw)) } else { return res } }});(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { const method = Array.prototype[key] as any arrayInstrumentations[key] = function (this: unknown[], ...args: unknown[]) { pauseTracking() const res = method.apply(this, args) enableTracking() return res }})
function createGetter(isReadonly = false) { return function get(target: object, key: string | symbol, receiver: object): any { // 如果进入到get方法,说明肯定是一个proxy代理对象 // 如果访问的是__v_isReactive,返回true if (key === ReactiveFlags.IS_REACTIVE) { return true } else if (key === ReactiveFlags.IS_READONLY) { // 如果访问的是ReactiveFlags.IS_READONLY, 返回true return isReadonly } else if (key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap : targetMap).get(target)) { // 访问的是 __v_raw 属性,并且是代理对象本身在访问 return target }
// 只有在非只读的情况下才会收集依赖 if (!isReadonly) { track(target, TrackOpTypes.GET, key) }
// 判断是不是数组,如果是数组,并且 key 是 arrayInstrumentations 对应的方法 const targetIsArray = isArray(target) if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver) }
// 返回对象的相应属性值 const result = Reflect.get(target, key, receiver)
// 判断是不是对象,是对象就递归代理 // 如果整个对象是只读的,那么这个对象的属性是对象,也应该是只读的 if (isObject(result)) { return isReadonly ? readonly(result) : reactive(result) }
return result }}
function set(target: Record<string | symbol, unknown>, key: string | symbol, value: unknown, receiver: object): boolean { // 判断是新增还是修改 const type = hasOwn(target, key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD
const oldValue = target[key]
const targetIsArray = isArray(target)
// 旧值的长度 const oldLen = targetIsArray ? target.length : 0
// 设置对象的相应属性值 const result = Reflect.set(target, key, value, receiver)
if (!result) { return result }
// 这里代表设置成功 const newLen = targetIsArray ? target.length : 0
if (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD) { trigger(target, type, key) if (targetIsArray && oldLen !== newLen) { // 数组长度变化了,但是不是直接改的 length 属性 if (key !== 'length') { trigger(target, TriggerOpTypes.SET, 'length') } else { // 操作的是 key,并且 key 的长度小于旧的长度,则需要删除(长度变长不需要处理) for (let i = newLen; i < oldLen; i++) { trigger(target, TriggerOpTypes.DELETE, i + '') } } } }
return result}
function has(target: object, key: string | symbol): boolean { // 收集依赖 track(target, TrackOpTypes.HAS, key) const result = Reflect.has(target, key) return result}
function ownKeys(target: object): (string | symbol)[] { track(target, TrackOpTypes.ITERATE, ITERATE_KEY) return Reflect.ownKeys(target)}
function deleteProperty(target: Record<string | symbol, unknown>, key: string | symbol) { // 判断对象是否有这个属性,不然删除就没有意义 const hadKey = hasOwn(target, key) // 删除是否成功的结果 const result = Reflect.deleteProperty(target, key) // 对象有这个属性,并且删除成功才会触发更新 if (hadKey && result) { trigger(target, TriggerOpTypes.DELETE, key) } return result}
const get = createGetter()const readonlyGet = createGetter(true)
export const mutableHandlers: ProxyHandler<object> = { get, set, has, ownKeys, deleteProperty,}
export const readonlyHandlers: ProxyHandler<object> = { get: readonlyGet, set(target, key) { console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target) return true }, deleteProperty(target, key) { console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target) return true },}import { isObject } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', RAW = '__v_raw',}
export interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean [ReactiveFlags.RAW]?: any}
// 分别存储普通代理和只读代理export const targetMap = new WeakMap<Target, any>()export const readonlyMap = new WeakMap<Target, any>()
function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>) { // 判断传入的是否是对象 if (!isObject(target)) { console.log('传入的必须是一个对象') return target }
// 判断是否已经被代理过 const proxyMap = isReadonly ? readonlyMap : targetMap const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy }
// 只要读到了__v_isReactive,就返回target // 因为Proxy对象直接拦截了这个属性 // 同样 读到target[ReactiveFlags.RAW]直接返回对象 if (target[ReactiveFlags.RAW] && target[ReactiveFlags.IS_REACTIVE]) { return target }
const proxy = new Proxy(target, baseHandlers)
// 存储代理对象 proxyMap.set(target, proxy)
return proxy}
export function reactive<T extends object>(target: T): Texport function reactive(target: object) { // 如果已经是只读代理,直接返回 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target }
// 第二个参数false,表示不是只读,并且传入的是 mutableHandlers return createReactiveObject(target, false, mutableHandlers)}
type DeepReadonly<T extends Record<string, any>> = T extends any ? { readonly [K in keyof T]: T[K] extends Record<string, any> ? DeepReadonly<T[K]> : T[K] } : never
export function readonly<T extends object>(target: T): DeepReadonly<T> { return createReactiveObject( target, true, readonlyHandlers // readonly的handler处理程序需要单独进行处理 )}
export function toRaw<T>(observed: T): T { return (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed}