本文基于
Vue 3.2.30
版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,大部分伪代码会采取js的形式展示,而不是原来的ts代码本文不讲解shallowRef、shallowReactive、shallowReadonly中使用集合类(Map、Set)的相关内容
本文内容
- ref数据类型初始化、依赖收集、派发更新以及常用方法等内容结合源码的分析
- readonly数据类型应用场景、初始化、依赖收集、派发更新等内容结合源码的分析
- 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.value
的get
获取最新的数据,重新触发依赖收集,并且返回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
的响应式是拦截get
和set
请求,然后在get
和set
请求进行track
和trigger
,进行依赖收集和派发更新,Ref
数据是使用.value的get
和.value的set
进行trackRefValue
依赖收集和triggerRefValue
派发更新 - 提供给响应式对象
Reactive
进行单个key
的响应式转化,通过.value
封装了响应式对象Reactive
某个key
的get
和set
请求细节,对于一些应用场景,比如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.value
的get
和set
方法,间接操作原数据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/737,Array
/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)
会触发original
的get()
方法,此时isReadonly=false
由于isReadonly=true
数据不用track
,因为它根本不能改变(包括set、delete、add
),不会改变自然也不需要追踪它的变化
if (!isReadonly) {
track(target, "get" /* GET */, key);
}
但是original(代理对象Parent)
的get()
方法中isReadonly=false
,因此会追踪它的变化,进行依赖收集,将当前的watchEffect
收集到origin
这个Proxy
的deps
中,依赖收集完成
(3) 派发更新
原始Proxy对象
触发更新
从下面的代码可以知道,当触发Proxy对象
的original.count set()
时,会触发派发更新,触发上面依赖收集
的effect
的重新执行
original这个Proxy
代理对象的isReadonly=false
,可以进行更改
document.getElementById("testBtn").addEventListener("click", () => {
// 更改源属性会触发其依赖的侦听器
original.count++
});
isReadonly=true
的Proxy对象
触发更新
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调试信息
shallowReadonly
1. 详细信息
readonly() 的浅层作用形式,和 readonly()
不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为ref
的属性不会被自动解包了
2. 源码分析
(1) 初始化
从下面的代码可以看出,本质上shallowReadonlyHandlers
初始化也是复用了readonlyHandlers
的set、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) 依赖收集
(4) 派发更新
copy.count = new Date().getTime()
会直接触发readonly
的set()
方法,然后直接提示错误,返回true
,什么操作都不会触发copy.count.temp = new Date().getTime()
会触发派发更新,因为根据上面依赖收集的分析,由于shallowReadonly的特性
,copy.count
是一个reactive对象
,而不是一个readonly对象
,因此它会收集依赖以及派发更新,流程图如下:
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)
进行深度key
的Proxy
转化
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)
进行深度key
的Proxy
转化,因此表现为如下:
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
才会触发Proxy
的set
方法,触发响应式派发更新,触发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();
});
结束语
ref
、shallow
、readonly
类型数据的代码逻辑分散在各个地方,本文写在最后,仍然觉得还有很多细小地方没有进行分析,如果有读者发现文章中有部分源码情况没有覆盖,麻烦在评论区中告知,我将尽快补充完善
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。