跳转到内容

readonly

readonly 其实也是通过 reactive 来实现的,只是在 reactive 中添加了一个参数,来指定是否是只读代理。

所以只需要处理 reactive 函数就行了,所以可以修改下之前的 reactive 函数。使用一个高阶函数 createReactiveObject 来实现。

readonly 对应的方法也会变化,比如不能修改,删除等操作,所以可以把 handlers 也传到 createReactiveObject 函数中。

vue-source/packages/reactivity/src/reactive.ts
// 分别存储普通代理和只读代理
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 函数

vue-source/packages/reactivity/src/reactive.ts
export function reactive<T extends object>(target: T): T
export function reactive(target: object) {
// 如果已经是只读代理,直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 第二个参数false,表示不是只读,并且传入的是 mutableHandlers
return createReactiveObject(target, false, mutableHandlers)
}

添加 readonly 函数

vue-source/packages/reactivity/src/reactive.ts
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 函数。

vue-source/packages/reactivity/src/baseHandlers.ts
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

vue-source/packages/reactivity/src/baseHandlers.ts
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 = 2
console.log(readonlyProxy.a)
// 嵌套的对象的属性也不能修改
readonlyProxy.c.d = 22
console.log(readonlyProxy.c.d)

20260401171929

最后贴一下修改完的完整代码 effect 和 reactive 文件

vue-source/packages/reactivity/src/baseHandlers.ts
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
},
}
vue-source/packages/reactivity/src/reactive.ts
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): T
export 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
}