2

本文基于Vue 3.2.30版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,大部分伪代码会采取js的形式展示,而不是原来的ts代码

本文不讲解shallowRef、shallowReactive、shallowReadonly中使用集合类(Map、Set)的相关内容

本文内容

  1. ref数据类型初始化、依赖收集、派发更新以及常用方法等内容结合源码的分析
  2. readonly数据类型应用场景、初始化、依赖收集、派发更新等内容结合源码的分析
  3. shallowRef、shallowReactive、shallowReadonly数据依赖收集、派发更新等内容结合源码的分析
文章部分内容摘录于Vue3官方文档

Ref

1. 初始化

  • 初始化dep,后面用来存储effects,进行依赖收集和派发更新
  • __v_isRef=true
  • 如果传递的value是原始值,那么this._rawValue=原始值this._value=原始值
class RefImpl {
    constructor(value, __v_isShallow) {
        this.__v_isShallow = __v_isShallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value);
        this._value = __v_isShallow ? value : toReactive(value);
    }
}
function toRaw(observed) {
    const raw = observed && observed["__v_raw" /* RAW */];
    return raw ? toRaw(raw) : observed;
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;
function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    if (!isObject(target)) { return target; }
}
  • 如果传递的value是响应式对象,那么this._rawValue=原始对象this._value=响应式对象
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {

    if (!isObject(target)) { return target; }

    // target is already a Proxy, return it.
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
}
  • 如果传递的value是对象,那么this._rawValue=原始对象this._value=响应式对象
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // ...target是原始值/响应式对象的处理

    // target是原始对象的处理
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

2. 依赖收集

从之前的文章Vue3源码-响应式系统-依赖收集和派发更新流程浅析可以知道,当effect触发Ref.value数据时,会进行依赖收集
从下面的代码块可以知道,当触发依赖收集时,Ref.dep会收集目前的effect

get value() {
    trackRefValue(this);
    return this._value;
}
function trackRefValue(ref) {
    if (shouldTrack && activeEffect) {
        ref = toRaw(ref);
        trackEffects(ref.dep || (ref.dep = createDep());
    }
}
function trackEffects(dep, debuggerEventExtraInfo) {
    let shouldTrack = false;
    if (!newTracked(dep)) {
        dep.n |= trackOpBit; // set newly tracked
        shouldTrack = !wasTracked(dep);
    }
    if (shouldTrack) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
    }
}

3. 派发更新

从之前的文章Vue3源码-响应式系统-依赖收集和派发更新流程浅析可以知道,当effect重新执行时,即触发effect.run()时,会触发Ref.valueget获取最新的数据,重新触发依赖收集,并且返回effect.run()的值

set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal);
    if (hasChanged(newVal, this._rawValue)) {
        this._rawValue = newVal;
        this._value = this.__v_isShallow ? newVal : toReactive(newVal);
        triggerRefValue(this, newVal);
    }
}
function triggerRefValue(ref, newVal) {
    ref = toRaw(ref);
    if (ref.dep) {
        triggerEffects(ref.dep);
    }
}
function triggerEffects(dep, debuggerEventExtraInfo) {
    for (const effect of isArray(dep) ? dep : [...dep]) {
        if (effect !== activeEffect || effect.allowRecurse) {
            if (effect.scheduler) {
                effect.scheduler();
            }
            else {
                effect.run();
            }
        }
    }
}

4. Ref类型数据结构应用总结

  • 追踪原始值的变化,使用.value作为一种特殊的key,类似于Proxy的响应式是拦截getset请求,然后在getset请求进行tracktrigger,进行依赖收集和派发更新,Ref数据是使用.value的get.value的set进行trackRefValue依赖收集和triggerRefValue派发更新
  • 提供给响应式对象Reactive进行单个key的响应式转化,通过.value封装了响应式对象Reactive某个keygetset请求细节,对于一些应用场景,比如setup()返回整个响应式对象Reactive时,想要布局直接写count,而不是写reactiveObject.count时,Ref提供了给响应式对象Reactive一种不失去响应式的解构功能
上面已经分析了第一点相关应用,下面流程将讲解第二点

5. Ref和Reactive的相互转化

(1) toRef

应用示例
setup(props) {
    const obj = reactive({ count: 1, temp: 2 });
    const countRef = toRef(obj, count);

    return {
        countRef
    }
}
源码分析

本质是模仿原始值的封装方式,将reactive(obj)的每一个key对应的数据value当作一个原始值
新建一个代理对象ObjectRefImpl,初始化时传入key+原数据Object,通过代理对象ObjectRefImpl.valuegetset方法,间接操作原数据Object,相当于代理对象ProxyObject封装了原数据Object某个key的所有操作逻辑,我们只关心代理对象ProxyObject对应的key即可

  • get请求countRef初始化时已经传入key,使用countRef.value获取值,触发的是初始化传入的this._object[this._key]
  • set请求countRef初始化时已经传入key,使用countRef.value设置值,触发的是初始化传入的this._object[this._key]=xxxxx
function toRef(object, key, defaultValue) {
    const val = object[key];
    return isRef(val)
        ? val
        : new ObjectRefImpl(object, key, defaultValue);
}

class ObjectRefImpl {
    constructor(_object, _key, _defaultValue) {
        this._object = _object;
        this._key = _key;
        this._defaultValue = _defaultValue;
        this.__v_isRef = true;
    }
    get value() {
        const val = this._object[this._key];
        return val === undefined ? this._defaultValue : val;
    }
    set value(newVal) {
        this._object[this._key] = newVal;
    }
}

(2) toRefs

应用示例
setup(props) {
    const obj = reactive({ count: 1, temp: 2 });
    const objRef = toRefs(obj, count);

    return {
        ...objRef
    }
}
源码分析

toRefs本质也是使用toRef进行某一个key的代理对象的创建,只不过toRefs是进行所有key的所有代理对象的创建

function toRefs(object) {
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key);
    }
    return ret;
}

toRefs又新建了一个代理对象,使用key作为索引,将所有toRef创建的对象进行存储,最终形成

toRefsObject: {
    [key1]: {
        get value() {
            // 操作reactiveObject[key1]
        },
        set value() {
            // 操作reactiveObject[key1]
        }
    },
    [key2]: {
        get value() {
            // 操作reactiveObject[key2]
        },
        set value() {
            // 操作reactiveObject[key2]
        }
    },
}

(3) 使用Ref作为Reactive的一个属性,自动解包value进行更新和获取值

应用示例
const countRef = ref(0);
const obj = reactive({count: countRef});

obj.count = 2222; // 不用写value,自动更新countRef
源码分析

从下面的源码可以看出,如果使用了Ref作为其中一个属性,那么触发set操作时会自动进行判断传入的值是不是原始值,然后自动触发oldValue.value= newValue

target必须不是Array类型,为什么不是Array类型请看下面ref 在响应式对象/Array中不进行解包的分析
function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        let oldValue = target[key];
        if (!shallow && !isReadonly(value)) {
            if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value;
                return true;
            }
        }
    }
}

6. Ref和watch

(1) 为什么watch(Ref)是浅监听,而watch(Reactive)是深度监听呢?

  • 由下面代码块可以发现,如果初始化传入一个Ref数据,则直接获取Ref.value,此时deep=false,只有传入一个Reactive数据时,才会触发deep=true
  • deep直接影响了getter的构建,即deep=true=>getter = () => traverse(baseGetter())
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ) {
    if (isRef(source)) {
        getter = () => source.value;
        forceTrigger = isShallow(source);
    }
    else if (isReactive(source)) {
        getter = () => source;
        deep = true;
    } else if (isArray(source)) {
        isMultiSource = true;
        forceTrigger = source.some(isReactive);
        getter = () => source.map(s => {
            if (isRef(s)) {
                return s.value;
            }
            // ....
        });
    }

    if (cb && deep) {
        const baseGetter = getter;
        getter = () => traverse(baseGetter());
    }
}

(2) 如果传入一个Reactive数据,再传入deep=false会怎么样?

从上面的源码看出,就算你传入deep=false,最终也会被强制转化deep=true

watch还支持传入function类型,即getter=()=> reactive.count,这样就能实现监听reactive.count的目的,而不是深度监听

(3) watch支持监听原始值吗?

从上面的代码块可以发现,如果想要监听原始值,我们得传入Ref数据或者使用function作为source,即

const countRef = ref(333);
watch(countRef, ()=> {
  // 可以监听countRef的改变
});
countRef.value = 555;

const obj = reactive({count: 333});
watch(obj, ()=> {
  // 可以监听obj.count的改变
});
watch(obj.count, ()=> {
  // 无法监听obj.count的改变,因为obj.count是原始值
  // 在上面doWatch源码中,getter为空,最终不会执行obj.count
  // 因为无法触发obj-Proxy的get请求触发依赖收集
});
watch(()=> obj.count, ()=> {
  // 可以监听obj.count的改变
  // obj-Proxy的get请求触发依赖收集
});
obj.count = 4444;

7. ref 在响应式对象/Array中不进行解包

参考 vuejs/core/issues/737Array/Map/Set将不会解包ref数据

只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包,如下面代码块所示,嵌套在一个深层响应式对象内时会自动解包

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包,如下面代码块所示,不会自动进行解包

const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

readonly

1. 详细信息

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的

const original = reactive({ count: 0 });
const copy = readonly(original);
watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count);
})

// 更改源属性会触发其依赖的侦听器
original.count++;
// 更改该只读副本将会失败,并会得到一个警告
copy.count++; // warning!

2. 源码分析

结合上面应用场景的代码块进行分析,请注意,上面readonly包裹的是一个Proxy对象,而不是简简单单一个Object对象,这种特殊复杂的例子可以帮助我们更好理解整个流程

(1) 初始化

从下面的源码可以看出,const copy = readonly(original)会将target=Proxy对象传入,为original这个Proxy对象再构建一个new Proxy()对象,我们成为代理对象Child代理对象original我们成为代理对象Parent

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // 如果是非读的Proxy,直接返回,不再进行转化
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
    // isReadonly=true时,第二个参数是readonlyHandlers,即new Proxy(target,readonlyHandlers)
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

(2) 依赖收集

watchEffect触发copy.count时,由于copy是一个Proxy对象,因此会触发它的get()方法,此时isReadonly=true

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver);
    }
}

从而触发了Reflect.get(target, key, receiver),由于copy(代理对象Child)实际上是为了target=original(代理对象Parent)建立的Proxy对象,因此Reflect.get(target, key, receiver)会触发originalget()方法,此时isReadonly=false

由于isReadonly=true数据不用track,因为它根本不能改变(包括set、delete、add),不会改变自然也不需要追踪它的变化

if (!isReadonly) {
    track(target, "get" /* GET */, key);
}

但是original(代理对象Parent)get()方法中isReadonly=false,因此会追踪它的变化,进行依赖收集,将当前的watchEffect收集到origin这个Proxydeps中,依赖收集完成

(3) 派发更新

原始Proxy对象触发更新

从下面的代码可以知道,当触发Proxy对象original.count set()时,会触发派发更新,触发上面依赖收集effect的重新执行

original这个Proxy代理对象的isReadonly=false,可以进行更改
document.getElementById("testBtn").addEventListener("click", () => {
    // 更改源属性会触发其依赖的侦听器
    original.count++
});
isReadonly=trueProxy对象触发更新
document.getElementById("testBtn1").addEventListener("click", () => {
    // 更改该只读副本将会失败,并会得到一个警告
    copy.count++ // warning!
});

当触发上面的代码块copy.count++,即触发Proxy对象copy.count set()时,由下面初始化的代码可以知道,set()方法直接拦截返回true,然后弹出警告,copy.count++被阻止执行了

const readonlyHandlers = {
    get: readonlyGet, // 本质上还是createGetter()
    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;
    }
};
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    const proxy = new Proxy(target, readonlyHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

3. 多层嵌套场景-源码分析

(1) 示例

const original = reactive({ count: { temp: 333, temp1: 455 }, count1: 4444 });
const copy = readonly(original);
watchEffect(() => {
    console.info("----------------------------------------------")
    // 用来做响应性追踪
    console.log(copy.count.temp);
    console.info("----------------------------------------------")
});
document.getElementById("testBtn2").addEventListener("click", () => {
    copy.count.temp = new Date().getTime();
});

(2) 派发更新源码更新

下面图片展示的是派发更新流程,而依赖收集跟派发更新核心流程大同小异,主要流程为readonly->reactive->对象->返回reactive(object)->返回readonly(reactive)->reactive->原始对象获取原始值->返回原始值给reactive->返回原始值给readonly,显示在界面,将不再展示依赖收集的流程
详细可以查看运行github-readonly.html后的console调试信息

Vue3-readonly-example.svg

shallowReadonly

1. 详细信息

readonly() 的浅层作用形式,和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为ref的属性不会被自动解包了

2. 源码分析

(1) 初始化

从下面的代码可以看出,本质上shallowReadonlyHandlers初始化也是复用了readonlyHandlersset、delete,然后稍微改变了createGetter()的传值,都为true

new Proxy()的代码都是同样的逻辑,这里不再赘述,唯一区别就是new Proxy(target, handler)传入的第二个参数,这里传入的是shallowReadonlyHandlers(暂时不考虑集合类)
const shallowReadonlyHandlers = /*#__PURE__*/ extend({}, readonlyHandlers, {
    get: shallowReadonlyGet
});
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        //.......
        const res = Reflect.get(target, key, receiver);
        if (shallow) {
              // 值为 ref 的属性不会被自动解包了,因为在解包前就return了
            return res;
        }
        if (isRef(res)) {
            // ref unwrapping - does not apply for Array + integer key.
            const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
            return shouldUnwrap ? res.value : res;
        }
        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res);
        }
        return res;
    }
}

(2) 代码示例

const original = reactive({ count: { temp: 3 } })
const copy = shallowReadonly(original)

effect(() => {
    console.warn("===========copy effect触发===========");
    console.log(copy.count.temp);
    console.warn("===========copy effect触发===========");
});

document.getElementById("testBtn").addEventListener("click", () => {
    copy.count = new Date().getTime();
});
document.getElementById("testBtn1").addEventListener("click", () => {
    copy.count.temp = new Date().getTime();
});

(3) 依赖收集

Vue3-shallowReadonly-依赖收集.svg

(4) 派发更新

  • copy.count = new Date().getTime()会直接触发readonlyset()方法,然后直接提示错误,返回true,什么操作都不会触发
  • copy.count.temp = new Date().getTime()会触发派发更新,因为根据上面依赖收集的分析,由于shallowReadonly的特性copy.count是一个reactive对象,而不是一个readonly对象,因此它会收集依赖以及派发更新,流程图如下:
Vue3-shallowReadonly-派发更新.svg

shallowRef

1. 详细信息

Vue 的响应性系统默认是深度的。在数据量巨大时,深度响应性也会导致不小的性能负担,因为每个属性访问都将触发代理的依赖追踪
Vue 确实也为此提供了一种解决方案,通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新

const shallowArray = shallowRef([
  /* 巨大的列表,里面包含深层的对象 */
])

// 这不会触发更新...
shallowArray.value.push(newObject)
// 这才会触发更新
shallowArray.value = [...shallowArray.value, newObject]

// 这不会触发更新...
shallowArray.value[0].foo = 1
// 这才会触发更新
shallowArray.value = [
  {
    ...shallowArray.value[0],
    foo: 1
  },
  ...shallowArray.value.slice(1)
]

2. 常见用法

triggerRef()

强制触发依赖于一个浅层 ref 的副作用,如下面代码块所示,triggerRef可以强制触发shallowRef依赖收集的watchEffect的重新执行

const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 强制触发shallowRef依赖收集的effect的重新执行
// 触发上面的watchEffect打印 "Hello, universe"
triggerRef(shallow)

3. 源码分析

如果初始化传入value也是Ref对象,从下面源码可以知道,只会直接返回Ref对象,不会启动new RefImpl初始化流程
function shallowRef(value) {
    return createRef(value, true);
}
function createRef(rawValue, shallow) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

从下面代码可以知道,当__v_isShallow=true

  • 初始化this._rawValue = value,而不会进行toRaw(value)获取原始数据,当数据发生变化时,触发set value,也只会比较根元素hasChanged(newVal, this._rawValue)是否发生了改变,只会hasChanged()=true才会触发triggerRefValue派发更新
  • 初始化this._value = value,而不会进行toReactive(value)进行对象的深度响应式转化,因此只有根元素.value是有进行get value()set value(),只有根元素.value是响应式的,而深一层的对象是不会进行Proxy构建和方法拦截的,如const refCount = ref({count:1})

    • __v_isShallow = false时,访问refCount.value.count时会触发refCount get value()方法和Proxy({count: 1}) get()方法,因此会触发两个响应式变量,一个是RefImpl,一个是Proxy进行依赖收集
    • __v_isShallow = true时,访问refCount.value.count时因为{count: 1}不是响应式的,根元素才是响应式的,因此只会触发refCount get value()方法,触发RefImpl的依赖收集
class RefImpl {
    constructor(value, __v_isShallow) {
        this.__v_isShallow = __v_isShallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value);
        this._value = __v_isShallow ? value : toReactive(value);
    }
    get value() {
        trackRefValue(this);
        return this._value;
    }
    set value(newVal) {
        newVal = this.__v_isShallow ? newVal : toRaw(newVal);
        if (hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal;
            this._value = this.__v_isShallow ? newVal : toReactive(newVal);
            triggerRefValue(this, newVal);
        }
    }
}

shallowReactive

1. 详细信息

Vue 的响应性系统默认是深度的。在数据量巨大时,深度响应性也会导致不小的性能负担,因为每个属性访问都将触发代理的依赖追踪
Vue 确实也为此提供了一种解决方案,通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})
// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++

2. 源码分析

(1) 依赖收集

从下面的代码可以看出,shallow=true时,会直接将整个对象返回,而shallow=false时,还会深度遍历转化reactive(res)进行深度keyProxy转化

function createGetter(isReadonly = false, shallow = false) {
    return function get(target: Target, key: string | symbol, receiver: object) {
        const res = Reflect.get(target, key, receiver)
        if (!isReadonly) {
            track(target, TrackOpTypes.GET, key)
        }

        if (shallow) { return res }

        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res)
        }
        return res
    }
}

shallow=false时,比如const reactiveObj = reactive({count: {temp: 3}}),由于会调用reactive(res)进行深度keyProxy转化,因此表现为如下:

const reactiveObj = reactive({count: {temp: 3}})
effect(() => {
    // 结合上面源码块
    // reactiveObj.count触发reactiveObj的Proxy-createGetter()请求,获得 reactive({temp: 3}),称为代理对象Proxy-A
    // reactiveObj.count.temp触发"代理对象Proxy-A".temp,即触发"代理对象Proxy-A"的Proxy-createGetter()请求,获取3
    console.log(reactiveObj.count.temp);
});

shallow=true时,比如const reactiveObj1 = shallReactive({count: {temp: 3}}),由于if (shallow) { return res },因此表现为如下:

const reactiveObj1 = shallReactive({count: {temp: 3}})
effect(() => {
    // reactiveObj.count触发reactiveObj的Proxy-createGetter()请求,获得 {temp: 3},称为对象B
    // reactiveObj.count.temp触发"对象B".temp,不会触发任何Proxy-createGetter()请求,直接获取3
    console.log(reactiveObj1.count.temp);
});

(2) 派发更新

由上面依赖收集的分析可以知道,shallReactive只会触发根元素的依赖收集,也只会转化根元素为Proxy数据
继续使用const reactiveObj = reactive({count: {temp: 3}})这个例子,由于根元素才是Proxy对象,深层的key不会进行reactive()转化,因此reactiveObj1.count.temp=xxx不会触发任何响应式的派发更新,只有reactiveObj1.count才会触发Proxyset方法,触发响应式派发更新,触发effect的重新执行

const reactiveObj1 = shallReactive({count: {temp: 3}})
document.getElementById("testBtn").addEventListener("click", () => {
      // 不会触发Proxy-set方法,因为count:{temp: xxx}不是Proxy数据
    reactiveObj1.count.temp = new Date().getTime(); 
    // 会触发Proxy-set方法,因为根元素reactiveObj1:{count}是Proxy数据
    // 会触发Proxy.set(),进而触发派发更新
    reactiveObj1.count = new Date().getTime(); 
});

结束语

refshallowreadonly类型数据的代码逻辑分散在各个地方,本文写在最后,仍然觉得还有很多细小地方没有进行分析,如果有读者发现文章中有部分源码情况没有覆盖,麻烦在评论区中告知,我将尽快补充完善

参考资料

  1. 响应式 API:核心 | Vue.js
  2. 响应式 API:工具函数 | Vue.js
  3. 响应式 API:进阶 | Vue.js
  4. 性能优化 | Vue.js

Vue系列其它文章

  1. Vue2源码-响应式原理浅析
  2. Vue2源码-整体流程浅析
  3. Vue2源码-双端比较diff算法 patchVNode流程浅析
  4. Vue3源码-响应式系统-依赖收集和派发更新流程浅析
  5. Vue3源码-响应式系统-Object、Array数据响应式总结
  6. Vue3源码-响应式系统-Set、Map数据响应式总结
  7. Vue3源码-响应式系统-ref、shallow、readonly相关浅析
  8. Vue3源码-整体流程浅析
  9. Vue3源码-diff算法-patchKeyChildren流程浅析

白边
209 声望37 粉丝

源码爱好者,已经完成vue2和vue3的源码解析+webpack5整体流程源码+vite4开发环境核心流程源码+koa2源码