拆分 reactive 相关模块,并通过可配置的 getter 和 setter 实现 shallowReactive 与 readonly
代码拆分
虽然目前的 reactive 已经能工作,但依赖收集、代理拦截和代理创建都写在一起,违反了单一原则
将代码按职责拆成三个模块:
dep.ts:负责track、trigger和DepbaseHandlers.ts:负责Proxy的get和setreactive.ts:负责创建代理、缓存代理和暴露API
拆分 dep.ts
保存依赖收集和更新派发的逻辑。
import type { Link, ReactiveNode } from './system'
import { activeSub } from './effect'
import { link, propagate } from './system'
const targetMap: WeakMap<object, Map<unknown, Dep>> = new WeakMap()
export function track(target: object, key: unknown) {
if (!activeSub) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Dep()))
}
link(dep, activeSub)
}
export function trigger(target: object, key: unknown) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const dep = depsMap.get(key)
if (!dep) {
return
}
propagate(dep.subs)
}
class Dep implements ReactiveNode {
subs: Link
subsTail: Link
}
这样 deps.ts 存放的就是链表相关的逻辑
拆分 baseHandlers.ts
baseHandlers.ts 保存代理对象的拦截逻辑。
把 get 和 set 提前创建好,也可以避免每次调用 createReactiveObject 时都重新创建一份 handler 对象。
import { isRef } from '@vue/reactivity'
import { hasChanged, isObject } from '@vue/shared'
import { track, trigger } from './dep'
import { reactive, ReactiveFlags } from './reactive'
const get = createGetter()
const set = createSetter()
function createGetter() {
return function get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
if (key === ReactiveFlags.RAW) {
return target
}
track(target, key)
const res = Reflect.get(target, key, receiver)
if (isRef(res)) {
return res.value
}
if (isObject(res)) {
return reactive(res)
}
return res
}
}
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
const res = Reflect.set(target, key, value, receiver)
if (hasChanged(oldValue, value)) {
trigger(target, key)
}
return res
}
}
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
这样 baseHandlers.ts 只关心代理行为:
get中处理响应式标记、依赖收集、ref解包和嵌套对象代理set中处理ref写入、相同值判断和更新派发
改造 reactive.ts
reactive.ts 只负责创建代理对象。
import { isObject } from '@vue/shared'
import { mutableHandlers } from './baseHandlers'
export enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.RAW]?: any
}
const reactiveMap = new WeakMap<Target, any>()
export function reactive(target: object) {
return createReactiveObject(target, reactiveMap, mutableHandlers)
}
function createReactiveObject(
target: object,
proxyMap: WeakMap<Target, any>,
baseHandlers: ProxyHandler<object>
) {
if (!isObject(target)) {
return target
}
if ((target as Target)[ReactiveFlags.RAW]) {
return target
}
const existProxy = proxyMap.get(target)
if (existProxy) {
return existProxy
}
const proxy = new Proxy(target, baseHandlers)
proxyMap.set(target, proxy)
return proxy
}
export function isReactive(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
shallowReactive
shallowReactive 只代理对象第一层。
如果属性值还是对象,不会继续递归转成响应式对象。
it('shallowReactive 只处理第一层响应式', () => {
const state = shallowReactive({
a: {
b: 1
}
})
const fn = vi.fn()
effect(() => {
state.a.b
fn()
})
expect(isReactive(state)).toBe(true)
expect(isShallow(state)).toBe(true)
expect(isReactive(state.a)).toBe(false)
state.a.b = 2
expect(fn).toBeCalledTimes(1)
state.a = {
b: 3
}
expect(fn).toBeCalledTimes(2)
})
这个测试要验证三件事:
state本身是响应式对象state是浅层响应式对象- 修改深层属性
state.a.b不触发更新,替换第一层属性state.a会触发更新
所以 shallowReactive 的关键点在 get:
读取第一层属性时仍然会收集依赖,但拿到结果后直接返回,不能继续递归调用 reactive。
增加响应式标记
现在需要区分三类状态:
- 是否是
reactive - 是否是
readonly - 是否是
shallow
先增加对应的标记和判断方法。
export enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.IS_SHALLOW]?: boolean
[ReactiveFlags.RAW]?: any
}
export function readonly(target: object) {
}
export function shallowReactive(target: object) {
}
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}
这些标记和之前的 IS_REACTIVE、RAW 一样,并不是真实挂在对象上的属性,而是通过代理的 get 拦截返回。
改造 createGetter
reactive、shallowReactive 和 readonly 的读取行为很接近。
可以把差异收敛到两个参数里:
isReadonly:是否只读shallow:是否浅层代理
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
}
if (key === ReactiveFlags.RAW) {
return target
}
if (!isReadonly) {
track(target, key)
}
const res = Reflect.get(target, key, receiver)
if (shallow) {
return res
}
if (isRef(res)) {
return res.value
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
每个分支对应不同能力:
- 读取响应式标记时,直接返回当前代理类型
readonly不收集依赖shallow在读取第一层属性后直接返回,不处理ref解包和嵌套对象代理- 深层对象会根据当前类型继续转成
readonly或reactive
处理 readonly set
readonly 的读取可以复用 createGetter(true, false),但写入行为需要单独处理。
只读对象被修改时,不应该真正写入,也不应该触发更新。
function createReadonlySetter() {
return function set(target, key) {
console.warn(`${target} is readonly`)
return true
}
}
这里返回 true 是为了表示 set 拦截已经处理完成,避免在严格模式下因为返回 false 抛错。
组合 handlers
最后创建不同的 get 和 set,再组合成对应的 handlers。
const get = createGetter(false, false)
const readonlyGet = createGetter(true, false)
const shallowGet = createGetter(false, true)
const set = createSetter()
const readonlySet = createReadonlySetter()
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
export const shallowReactiveHandlers: ProxyHandler<object> = {
get: shallowGet,
set
}
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set: readonlySet
}
这样三个 API 就可以复用同一个 createReactiveObject:
const reactiveMap = new WeakMap<Target, any>()
const shallowReactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>()
export function reactive(target: object) {
return createReactiveObject(target, reactiveMap, mutableHandlers)
}
export function shallowReactive(target: object) {
return createReactiveObject(target, shallowReactiveMap, shallowReactiveHandlers)
}
export function readonly(target: object) {
return createReactiveObject(target, readonlyMap, readonlyHandlers)
}