上一篇文章分析了reactive,readonly方法,这两种方法都是对target进行深度侦测(虽然是延迟侦测),Vue3.0还给我们提供了对target只进行第一层侦测,那就是shallowReactive,shallowReadonly方法。我们这篇文章看看这两个方法是怎么实现的。
先看个例子:
<html>
<head>
</head>
<body>
<div id="app">
<div>student class: {{state.student.name}}</div>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const {createApp, reactive, shallowReactive, readonly, shallowReadonly} = Vue
const app = createApp({
setup(){
let obj = {
msg: 'hello',
student: {
name: 'xiaoliu',
age: 20
}
}
let state = shallowReactive(obj)
return {state}
}
}).mount('#app')
setTimeout(() => {
console.log(`name is: ${app.state.student.name}`)
app.state.student.name = 'xiaowang'
console.log(`name after is: ${app.state.student.name}`)
}, 2000)
</script>
</body>
</html>
shallowReactive(obj)后更改student.name后虽然属性的值已经被更改了,但页面并没有更新。我们再看看shallowReadonly的例子:
<html>
<head>
</head>
<body>
<div id="app">
<div>student class: {{state.student.name}}</div>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const {createApp, reactive, shallowReactive, readonly, shallowReadonly} = Vue
const app = createApp({
setup(){
let obj = {
msg: 'hello',
student: {
name: 'xiaoliu',
age: 20
}
}
let state = shallowReadonly(obj)
return {state}
}
}).mount('#app')
setTimeout(() => {
console.log(`name is: ${app.state.student.name}, msg is: ${app.state.msg}`)
app.state.student.name = 'xiaowang'
app.state.msg = 'world'
console.log(`name after is: ${app.state.student.name}, msg is: ${app.state.msg}`)
}, 2000)
</script>
</body>
</html>
打印如下:
通过打印看出,不可以修改第一层属性,但可以修改除了第一层属性之外的深层次属性。修改第一层属性,在开发环境下给了个警告。
下面我们具体看看Vue3.0如何做到这一点的。
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
/**
* Return a shallowly-reactive copy of the original object, where only the root
* level properties are reactive. It also does not auto-unwrap refs (even at the
* root level).
*/
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers
)
}
对比reactive 和 shallowReactive,只是传给createReactiveObject方法的最后两个参数不同。这两个参数是用来传给new Proxy的第二个参数,我们接着往下看,
const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const set = /*#__PURE__*/ createSetter()
const shallowSet = /*#__PURE__*/ createSetter(true)
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
export const shallowReactiveHandlers: ProxyHandler<object> = extend(
{},
mutableHandlers,
{
get: shallowGet,
set: shallowSet
}
)
shallowReactiveHandlers对象覆盖了mutableHandlers对象的get,set方法,其他方法和mutableHandlers一样。且shallowGet 也调用的是createGetter方法,只是传递的参数不一样。shallowSet采用了同样的方式处理。所以重点看下createGetter,createSetter这两个方法。
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
//isReactive方法会走到这里
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
//isReadonly方法会走到这里
return isReadonly
} else if (
//toRaw方法会走这里
key === ReactiveFlags.RAW &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
const targetIsArray = isArray(target)
//这里没怎么看懂
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
//这里也没怎么看懂
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: isNonTrackableKeys(key)
) {
return res
}
//这里是关键,如果不是readonly的话,就进行track,也就是进行收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
//如果是shallowReactive,或shallowReadonly,则直接就return出去,不进行深度侦测
if (shallow) {
return res
}
//这里也没怎么看懂,先过去,之后再仔细研究
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
//这里是关键,如果不是shallow的,则判断res是否为对象,如果是对象的话,再递归进行reactive或readonly
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
return res
}
}
createGetter方法其实是对get方法的封装,这样在一个方法中就可以兼容处理reactive,readonly了。get方法主要是做了3件事,第一件是进行了依赖收集。第二件是根据参数判断是否进行深度侦测,如果是对象则进行递归进行侦测。第三件是判断isReactive,isReadonly。
下面看看createSetter方法,
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
//这里没怎么看懂,之后再仔细分析
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
}
//这里是关键,用来判断target是否有key属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
//不管是新增属性,还是更改属性值都要进行trigger,这样页面才能更新
if (!hadKey) {
//如果没有key属性,说明是新增
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
//如果有key属性,说明是更改属性值
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
createSetter方法也对set方法进行了封装,当新增属性或更改属性值的时候进行了trigger,从而更新页面。
最后我们顺便看下,isReactive,isReadonly,isProxy方法
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
如果value已经被reactive,则!!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]) 会触发get函数,get函数中会判断key是否为ReactiveFlags.IS_REACTIVE,如果是的话就返回true。isReadonly,处理过程是一样的。