summary
This article extracts the main parts of the Vue3 reactive source code to understand the implementation of its reactive object
. The source code of this article is based on this entry file: github reactive.ts
reactive()
Source code location: github reactive.ts
export function reactive(target) {
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
reactive
The createReactiveObject
function is called internally, and mutableHandlers
as the proxy handler
createReactiveObject()
Source code location: github reactive.ts
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
return proxy
}
It can be seen that the essence of ---c0ed896ef2e2dd601660e841e935db3d reactive
is to convert ordinary object
into Proxy
. And Proxy
only supports non-empty object
, so the reactive
function can only be used for object
.
const handler = {}
new Proxy({}, handler) // 正常运行
new Proxy(1, handler) // Cannot create proxy with a non-object as target or handler
If used for reactive primitive types, the ref
function provided by Vue3 should be used.
mutableHandlers
Source code location: github baseHandlers.ts
mutableHandlers
and mutableCollectionHandlers
new Proxy
used for the second parameter of ---25416f23ba8ac4224c5996d6e2fbe8d7---, which is the core logic of get
reactive
the most important function get
and set
attributes.
export const mutableHandlers = {
get: createGetter(),
set: createSetter(),
deleteProperty,
has,
ownKeys,
}
createGetter()
Source code location: github baseHandlers.ts
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 里面代码比较长,下面拆成几部分理解
// part 1 - 处理内部变量
// part 2 - 处理数组
// part 3 - 处理其他情况
}
}
part 1 - Handling internal variables
// ts语法
const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
The objects generated using reactive()
all contain the attributes corresponding to ReactiveFlags
, and you can output the effect: RunJS Demo .
Using the object generated by shallowReactive()
, only the properties of the root node are responsive, which is the same as the characteristics of responsive variables in Vue2. See this example: Vue3 shallowReactive
part 2 - working with arrays
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
Proxy is an array, and when calling the array prototype method, it is actually first get
to the method name, and then use this method to set
the corresponding value, such as the following code:
const arr = [{count: 1}, {count: 2}]
const proxyArray = new Proxy(arr, {
get(target, prop, receiver) {
console.log(`get ${prop}`)
return Reflect.get(target, prop, receiver)
},
set(target, prop, receiver) {
console.log(`set ${prop}`)
return Reflect.set(target, prop, receiver)
}
})
proxyArray.push({count: 3})
// output sequentially as
get push
get length
set 2
set length
See the actual operation effect: Javascript Proxy an array
part 3 - dealing with other cases
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
If the variable initialized with reactive
contains a value of type ref
, it is not necessary to bring .value
when taking the value. E.g:
const count = ref(0)
const state = reactive({count: count})
// 以下两行,都能取到count值
console.log(count.value)
console.log(state.count)
See the actual operation effect: Vue3 reactive object with ref
createSetter()
Source code location: github baseHandlers.ts
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// part 1 - 赋值
// part 2 - 触发事件
}
}
part 1 - assignment
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
part 2 - Detach the event
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
Only when adding ( TriggerOpTypes.ADD
) and setting a new value ( TriggerOpTypes.SET
) will trigger (trigger) subscribed events
Epilogue
Original link: https://runjs.work/projects/a082a7c4e4b748a8
The original example is kept updated.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。