We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文使用的vue版本为3.2.26。在阅读 ref 源码之前,我们需要知道它的特性,可以通过阅读单例测试源码或者是阅读官网的 API了解特性,推荐阅读单例,了解特性在后面阅读时才能更好理解。
vue
3.2.26
ref
API
我们前两章讲讲解了reactive源码解析和effect源码解析,并且知道了它们是如何实现响应式的,还没看过的小伙伴可以先阅读一下。我们回顾一下,reactive函数可以创建通过Proxy实现的响应式对象,响应式对象需要在effect中使用才能收集到依赖,在更改响应式对象时,代理会通过trigger通知所有依赖的effect对象,并执行effect的监听方法。
reactive
Proxy
effect
trigger
因为reactive创建的响应式对象是通过Proxy来实现的,所以传入数据不能为基础类型,比如number、string、boolean。创建回来的对象必须保持它的引用,不能重新赋值,这样会失去响应式的特征,比如:
number
string
boolean
import { reactive, effect } from 'vue' let reuser = reactive({ name: 'bill', sex: '男', age: 18 }) effect(() => { console.log(`${reuser.name}的性别为${reuser.sex},年龄为${reuser.age}岁`) }) let change = { sex: '女', age: 19 } // 更改了proxy的指向,失去响应式特征,并且在下一次垃圾回收会回收掉之前的proxy reuser = { ...reuser, ...change }
上方代码为了合并修改项将两个对象展开到一个对象中,这样更改会丢失proxy的指向,失去响应式特征,并且在下一次垃圾回收会回收掉之前的proxy,所以只能够找到修改属性,在原来的proxy基础上一个一个修改。
proxy
ref对象是对reactive不支持的数据的一个补充,让如基础数据响应式进行支持,以及更方便的对象替换操作推出的。下面我们先了解一下ref的特性。
使用ref或shallowRef函数创建ref对象,ref通过value属性进行访问和修改传入参数。
shallowRef
value
与reactive不同,ref的参数没有任何限制。
使用reactive可接受的对象为ref参数对象时,isReactive(ref.value)为true。
isReactive(ref.value)
true
ref在effect监听函数中使用可响应式
ref在effect中只有value属性是可响应式的
customRef可以创建自定义getter、setter的ref,创建时需要提供一个创建get, set工厂方法,工厂方法会传入收集方法和触发方法,由用户主动触发。如
customRef
getter
setter
get, set
let value = 1 const custom = customRef((track, trigger) => ({ get() { track() return value }, set(newValue: number) { value = newValue trigger() } }))
使用toRef可以通过proxy的某个属性生成为可以有默认值的ref对象
toRef
使用toRefs可以通过proxy的数据结构以及所有属性,生成与proxy数据结构一致的,所有属性值为ref对象的对象
toRefs
综合上面的特性和之前讲解effect的实现原理,能猜得到ref对象会对value属性的修改和获取时进行拦截,在value被get的时候收集依赖,在set的时候获取依赖关联的effect再触发依赖函数。ref对属性修改和获取时不能通过proxy来实现,ref支持基础类型而proxy不支持。收集依赖时不能使用effect文件中的targetMap关联effect,targetMap是WeakMap类型,WeakMap类型仅支持对象作为key,不支持基础类型。
get
set
targetMap
WeakMap
key
接下来我们看看ref和shallowRef的具体实现:
// 是否是ref根据属性的__v_isRef决定 export function isRef(r: any): r is Ref { return Boolean(r && r.__v_isRef === true) } export function ref(value?: unknown) { return createRef(value, false) } export function shallowRef(value?: unknown) { return createRef(value, true) } // 创建ref对象,传入raw和是否是shallow function createRef(rawValue: unknown, shallow: boolean) { // 如果之前时ref则直接返回 if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }
与reactive一样ref创建的响应式对象也分为是否是shallow,ref对象支持对value深度响应式,也就是说ref.value.a.b中的修改都能被拦截,shallowRef对象只支持对value值的响应式。
shallow
ref.value.a.b
ref和shallowRef函数都使用createRef来创建ref对象,只是参数的区别。创建的ref对象会附加__v_isRef属性来标识是否是ref对象。在创建ref对象之前会检查入参是否是ref如果是就直接返回入参参数。
createRef
__v_isRef
我们看到ref函数创建的真实对象是RefImpl,采用了class写法,将raw和shallow作为构造函数,下面我们看看这个class的实现:
RefImpl
class
raw
// Ref对象类 class RefImpl<T> { // 存放 reactive(raw) 后的proxy private _value: T // 存放 raw private _rawValue: T // 建立与effect的关系 public dep?: Dep = undefined // 是否ref的标识 public readonly __v_isRef = true // 构造,传入raw 和 shallow constructor(value: T, public readonly _shallow: boolean) { // 存储 raw this._rawValue = _shallow ? value : toRaw(value) // 如果是不是shallow则 存储 reactive proxy 否则存储传入参数 this._value = _shallow ? value : toReactive(value) } // getter value拦截器 get value() { // track Ref 收集依赖 trackRefValue(this) return this._value } // setter value拦截器 set value(newVal) { // 如果是需要深度响应的则获取 入参的raw newVal = this._shallow ? newVal : toRaw(newVal) // 查看要设置值是否与当前值是否修改 if (hasChanged(newVal, this._rawValue)) { // 存储新的 raw this._rawValue = newVal // 更新value 如果是深入创建的还需要转化为reactive代理 this._value = this._shallow ? newVal : toReactive(newVal) // 触发value,更新关联的effect triggerRefValue(this, newVal) } } }
如果不是shallow传入的value会通过toReactive转化为reactive,然后存在ref._value中。在get的时候直接返回这个reactive,这就是使用reactive可接受的对象为ref参数对象时,isReactive(ref.value)为true的原因,也是为什么能深度响应的原因。
toReactive
ref._value
ref还会存储入参和set的原始值,如果不是shallow则通过toRaw获取,存储在_rawValue属性中,存储这个值是为了能正确的判断值是否被修改。所以下方这种情况是不会调用triggerRefValue的,因为原始值是一样的。
toRaw
_rawValue
triggerRefValue
const target = { name: 'bill' } const reTarget = reactive(target) const targetRef = ref(reTarget) targetRef.value = target
ref对象还有个非常重要的属性dep,这里的dep与我们之前在effect一章讲的dep是一样的,没看过的同学可以去看一下。我们简单回顾一下。
dep
reactive对象是通过targetMap与Dep关联的。reactive收集时通过track函数获取dep,然后通过dep对象调用trackEffects函数来将effect与Dep关联。
Dep
track
trackEffects
reactive触发时通过trigger函数整理相关联的多个dep最终合并成一个dep,然后通过dep调用triggerEffects获取关联的effect收集函数并触发。
triggerEffects
dep中的具体细节管理是通过trackEffects函数和effect对象管理的,将dep与effect是由trackEffects函数处理的, 触发是由triggerEffects函数执行的。
也就是说基于现有effect的基础上,创建响应式对象只需要收集时获取dep并调用trackEffects(dep), 触发时获取收集时的dep并调用triggerEffects(dep)。dep属性就是ref能成为响应式对象的根本原因。
trackEffects(dep)
triggerEffects(dep)
接下来我们看看ref是如何实现trackEffects(dep)和triggerEffects(dep)的。ref在get value时会调用trackRefValue,在set value时,如果value值发生了更改则调用triggerRefValue。可以猜到这两个方法就是实现响应式的关键,接下来我们看看他们的具体实现
get value
trackRefValue
set value
// 收集 ref 依赖 调用trackEffects(dep) export function trackRefValue(ref: RefBase<any>) { // 如果当前开启了跟踪 if (isTracking()) { // 获取raw ref数据 ref = toRaw(ref) // 如果当前ref还未初始化dep则创建 if (!ref.dep) { ref.dep = createDep() } // 如果是开发环境,则传入track细节, if (__DEV__) { trackEffects(ref.dep, { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep) } } } // 触发 ref 调用trackEffects(dep) export function triggerRefValue(ref: RefBase<any>, newVal?: any) { // 获取raw ref数据 ref = toRaw(ref) // 如果当前ref 有关联的dep if (ref.dep) { // 如果当前是开发环境则发送具体触发细节 if (__DEV__) { triggerEffects(ref.dep, { target: ref, // SET引起的变化 type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } }
trackRefValue会在适当的时候初始化dep并调用trackEffects,triggerRefValue会获取ref的dep并调用triggerEffects,就是我们上面说的内容。
大家注意到传入的ref会调用toRaw方法来重新赋值,这个方法是获取reactive的原始数据的。因为用户可能使用reactive(ref(raw))来获取数据,如果直接使用可能会收集到dep属性的依赖。另外大家思考一下下面这段代码的effect监听函数会触发几次?
reactive(ref(raw))
const countRef = ref(0) const reCount = reactive(countRef) effect(() => { console.log(reCount.value) }) reCount.value = 3
答案是四次,第一次是首次收集依赖,reactive会收到value的获取,存储value属性的dep附加到targetMap。然后调用ref.value,ref在获取value时会调用trackRefValue,创建dep附加到自身属性上。注意ref.value返回this._value,这时候reactive收到_value属性的获取,存储_value属性的dep,附加到targetMap中。所以创建了三个dep。当发生更改新值存储到ref._value中,而对于reactive来说value和_value是完全没关联的所以会触发两次,而ref自身会触发一次没所以一共是四次。
ref.value
this._value
_value
接下来我们看看自定义ref方法customRef是如何实现的:
// 自定义ref对象类 class CustomRefImpl<T> { // 依赖dep 存储effets public dep?: Dep = undefined // 缓存getter setter private readonly _get: ReturnType<CustomRefFactory<T>>['get'] private readonly _set: ReturnType<CustomRefFactory<T>>['set'] // mark ref public readonly __v_isRef = true // 传入ref工厂函数 constructor(factory: CustomRefFactory<T>) { // 构建getter setter,传入track trigger函数 const { get, set } = factory( () => trackRefValue(this), () => triggerRefValue(this) ) this._get = get this._set = set } get value() { return this._get() } set value(newVal) { this._set(newVal) } } // 创建自定义ref export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> { return new CustomRefImpl(factory) as any }
customRef会创建CustomRefImpl的一个实例并返回,CustomRefImpl的实现和Ref差不多,使用trackRefValue和triggerRefValue将dep与effect关联实现响应式。不过CustomRefImpl会在工厂函数中传入trackRefValue和triggerRefValue,将收集依赖和触发执行权交给用户。让用户在适当的时候调用。在使用value时候调用生产的get方法,在设置value是调用生产的set方法。一般是在get的时候调用收集函数,set的时候触发函数。
CustomRefImpl
Ref
我们在使用reactive时通过缓存属性值很可能会失去响应式特性。因为属性值可能是reactive不支持深入响应的值,这时候缓存属性值,或者是通过ES6解构出来的值是不具备响应特性的。比如在下面这两种使用方式:
ES6
const reuser = reactive({ name: 'bill', sex: '男' }) const { name } = reuser const sex = reuser.sex
这样的话就得一直使用reuser.name的方式来进行访问,vue有两个api能很好的解决这个问题,就是toRef和toRefs。
reuser.name
api
toRef通过reactive代理和代理的某个属性生成为ref并且可以携带默认值。而toRefs根据reactive代理生成所有属性值为ref的对象。生成的ref的value是代理属性值的映射,两端更改都会实时同步,我们看看是如何使用的:
const reuser = reactive({ name: 'bill', sex: '男' }) const name = toRef(reuser, 'name', '未命名') const { sex } = toRefs(reuser) console.log(reuser.name, reuser.sex) //bill 男 console.log(name.value, sex.value) //bill 男 name.value = 'lzb' sex.value = '女' console.log(reuser.name, reuser.sex) //lzb 女 console.log(name.value, sex.value) //lzb 女 delete reuser.name console.log(reuser.name, reuser.sex) //undefined 女 console.log(name.value, sex.value) //未命名 女
接下来我们看看toRef和toRefs的具体实现:
// 将proxy对象和目标的属性 转化为ref 并拥有默认值 class ObjectRefImpl<T extends object, K extends keyof T> { public readonly __v_isRef = true constructor( // 记录代理 private readonly _object: T, // 要辅助的key private readonly _key: K, // 默认值 private readonly _defaultValue?: T[K] ) {} // 获取value get value() { const val = this._object[this._key] return val === undefined ? (this._defaultValue as T[K]) : val } set value(newVal) { this._object[this._key] = newVal } } // 将proxy对象和目标的属性 转化为ref 并拥有默认值 export function toRef<T extends object, K extends keyof T>( object: T, key: K, defaultValue?: T[K] ): ToRef<T[K]> { const val = object[key] return isRef(val) ? val : (new ObjectRefImpl(object, key, defaultValue) as any) } // 将proxy对象所有属性转化为ref值 export function toRefs<T extends object>(object: T): ToRefs<T> { // 只有内部代理才能toRefs if (__DEV__ && !isProxy(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`) } // 分别对所有属性toRef const ret: any = isArray(object) ? new Array(object.length) : {} for (const key in object) { ret[key] = toRef(object, key) } return ret }
toRef会先查看proxy[key]是否是ref如果是的话直接返回,如果不是则创建ObjectRefImpl并且将参数传入,ObjectRefImpl会标识当前对象是ref类型 (通过__v_isRef属性) ,并且缓存proxy、key和默认值。get value时直接通过proxy[key]来获取并返回,如果回去的值是undefined则使用默认值。set value时则通过proxy[key] = newVal来设置。
proxy[key]
ObjectRefImpl
undefined
proxy[key] = newVal
toRefs则是将每个属性都调用一次没有默认值的toRef,并且返回与proxy一致的数据结构。
为什么这里的ObjectRefImpl类不需要dep属性和收集依赖和触发更改呢?这是因为_object属性本身是proxy类型,当我们在使用proxy[key]就实现了收集依赖,在proxy[key] = newVal是就触发了更改。
_object
ref文件中还声明了其他辅助方法,比如triggerRef手动触发ref的更改使关联的effect重新执行收集函数;unref获取ref的原始值。这两个方法比较简单直接看源码即可,这里就不再讲解了。
triggerRef
unref
// 手动触发 ref export function triggerRef(ref: Ref) { // 开发环境用当前值做最新值变化 triggerRefValue(ref, __DEV__ ? ref.value : void 0) } // 解构ref,直接返回value export function unref<T>(ref: T | Ref<T>): T { return isRef(ref) ? (ref.value as any) : ref }
还有一个辅助方法proxyRefs,这个方法将一个对象直属属性内的所有ref属性值解构访问 (不需要通过value下标访问) 。什么是直属属性就是第一层属性,比如下方的代码:
proxyRefs
const name = ref('bill') const unUser = proxyRefs({ name: name, adderss: { city: ref('珠海') } }) console.log(unUser.name) // bill console.log(unUser.address.city) // Ref unUser.name = 'lzb' console.log(unUser.name) // lzb
proxyRefs只对第一层属性的ref解构。我们看看它的源码:
// 浅解构ref处理器 const shallowUnwrapHandlers: ProxyHandler<any> = { // getter将unref方便访问 get: (target, key, receiver) => { return unref(Reflect.get(target, key, receiver)) }, // setter先查看是否是ref,如果是则更新value set: (target, key, value, receiver) => { const oldValue = target[key] // 如果新数据不是ref但旧数据是则更新value if (isRef(oldValue) && !isRef(value)) { oldValue.value = value return true } else { return Reflect.set(target, key, value, receiver) } } } // 创建代理ref 解构方便使用 export function proxyRefs<T extends object>( objectWithRefs: T ): ShallowUnwrapRef<T> { // 如果是reactive对象则无需解构 return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers) }
当proxyRefs入参是reactive对象时则直接返回,reactive对象本身会对ref解构,而且是深度的,这里就不需要处理。有个特殊情况shallowReactive对象不会对ref解构,但是也直接返回了,也就是说这个方法对shallowReactive对象时无效的!
shallowReactive
如果proxyRefs入参不是reactive对象,则创建代理,get拦截器通过unref来获取值返回,set拦截器通过判断当前要更新的是否是ref如果是则更新value。
到这里我们ref的所有内容就已经讲完了,接下来日常小结。
上一章:vue3-effect源码解析
The text was updated successfully, but these errors were encountered:
No branches or pull requests
阅读准备
我们前两章讲讲解了reactive源码解析和effect源码解析,并且知道了它们是如何实现响应式的,还没看过的小伙伴可以先阅读一下。我们回顾一下,
reactive
函数可以创建通过Proxy
实现的响应式对象,响应式对象需要在effect
中使用才能收集到依赖,在更改响应式对象时,代理会通过trigger
通知所有依赖的effect
对象,并执行effect
的监听方法。因为
reactive
创建的响应式对象是通过Proxy
来实现的,所以传入数据不能为基础类型,比如number
、string
、boolean
。创建回来的对象必须保持它的引用,不能重新赋值,这样会失去响应式的特征,比如:上方代码为了合并修改项将两个对象展开到一个对象中,这样更改会丢失
proxy
的指向,失去响应式特征,并且在下一次垃圾回收会回收掉之前的proxy
,所以只能够找到修改属性,在原来的proxy
基础上一个一个修改。ref
对象是对reactive
不支持的数据的一个补充,让如基础数据响应式进行支持,以及更方便的对象替换操作推出的。下面我们先了解一下ref
的特性。使用
ref
或shallowRef
函数创建ref
对象,ref
通过value
属性进行访问和修改传入参数。与
reactive
不同,ref
的参数没有任何限制。使用
reactive
可接受的对象为ref
参数对象时,isReactive(ref.value)
为true
。ref
在effect
监听函数中使用可响应式ref
在effect
中只有value
属性是可响应式的customRef
可以创建自定义getter
、setter
的ref
,创建时需要提供一个创建get, set
工厂方法,工厂方法会传入收集方法和触发方法,由用户主动触发。如使用
toRef
可以通过proxy
的某个属性生成为可以有默认值的ref
对象使用
toRefs
可以通过proxy
的数据结构以及所有属性,生成与proxy
数据结构一致的,所有属性值为ref
对象的对象综合上面的特性和之前讲解
effect
的实现原理,能猜得到ref
对象会对value
属性的修改和获取时进行拦截,在value
被get
的时候收集依赖,在set
的时候获取依赖关联的effect
再触发依赖函数。ref
对属性修改和获取时不能通过proxy
来实现,ref
支持基础类型而proxy
不支持。收集依赖时不能使用effect
文件中的targetMap
关联effect
,targetMap
是WeakMap
类型,WeakMap
类型仅支持对象作为key
,不支持基础类型。ref和shallowRef
接下来我们看看
ref
和shallowRef
的具体实现:与
reactive
一样ref
创建的响应式对象也分为是否是shallow
,ref
对象支持对value
深度响应式,也就是说ref.value.a.b
中的修改都能被拦截,shallowRef
对象只支持对value
值的响应式。ref
和shallowRef
函数都使用createRef
来创建ref
对象,只是参数的区别。创建的ref
对象会附加__v_isRef
属性来标识是否是ref
对象。在创建ref
对象之前会检查入参是否是ref
如果是就直接返回入参参数。我们看到
ref
函数创建的真实对象是RefImpl
,采用了class
写法,将raw
和shallow
作为构造函数,下面我们看看这个class
的实现:如果不是
shallow
传入的value
会通过toReactive
转化为reactive
,然后存在ref._value
中。在get
的时候直接返回这个reactive
,这就是使用reactive
可接受的对象为ref
参数对象时,isReactive(ref.value)
为true
的原因,也是为什么能深度响应的原因。ref
还会存储入参和set
的原始值,如果不是shallow
则通过toRaw
获取,存储在_rawValue
属性中,存储这个值是为了能正确的判断值是否被修改。所以下方这种情况是不会调用triggerRefValue
的,因为原始值是一样的。ref
对象还有个非常重要的属性dep
,这里的dep
与我们之前在effect
一章讲的dep
是一样的,没看过的同学可以去看一下。我们简单回顾一下。reactive
对象是通过targetMap
与Dep
关联的。reactive
收集时通过track
函数获取dep
,然后通过dep
对象调用trackEffects
函数来将effect
与Dep
关联。reactive
触发时通过trigger
函数整理相关联的多个dep
最终合并成一个dep
,然后通过dep
调用triggerEffects
获取关联的effect
收集函数并触发。dep
中的具体细节管理是通过trackEffects
函数和effect
对象管理的,将dep
与effect
是由trackEffects
函数处理的, 触发是由triggerEffects
函数执行的。也就是说基于现有
effect
的基础上,创建响应式对象只需要收集时获取dep
并调用trackEffects(dep)
, 触发时获取收集时的dep
并调用triggerEffects(dep)
。dep
属性就是ref
能成为响应式对象的根本原因。接下来我们看看
ref
是如何实现trackEffects(dep)
和triggerEffects(dep)
的。ref
在get value
时会调用trackRefValue
,在set value
时,如果value
值发生了更改则调用triggerRefValue
。可以猜到这两个方法就是实现响应式的关键,接下来我们看看他们的具体实现trackRefValue
会在适当的时候初始化dep
并调用trackEffects
,triggerRefValue
会获取ref
的dep
并调用triggerEffects
,就是我们上面说的内容。大家注意到传入的
ref
会调用toRaw
方法来重新赋值,这个方法是获取reactive
的原始数据的。因为用户可能使用reactive(ref(raw))
来获取数据,如果直接使用可能会收集到dep
属性的依赖。另外大家思考一下下面这段代码的effect
监听函数会触发几次?答案是四次,第一次是首次收集依赖,
reactive
会收到value
的获取,存储value
属性的dep
附加到targetMap
。然后调用ref.value
,ref
在获取value
时会调用trackRefValue
,创建dep
附加到自身属性上。注意ref.value
返回this._value
,这时候reactive
收到_value
属性的获取,存储_value
属性的dep
,附加到targetMap
中。所以创建了三个dep
。当发生更改新值存储到ref._value
中,而对于reactive
来说value
和_value
是完全没关联的所以会触发两次,而ref
自身会触发一次没所以一共是四次。customRef
接下来我们看看自定义
ref
方法customRef
是如何实现的:customRef
会创建CustomRefImpl
的一个实例并返回,CustomRefImpl
的实现和Ref
差不多,使用trackRefValue
和triggerRefValue
将dep
与effect
关联实现响应式。不过CustomRefImpl
会在工厂函数中传入trackRefValue
和triggerRefValue
,将收集依赖和触发执行权交给用户。让用户在适当的时候调用。在使用value
时候调用生产的get
方法,在设置value
是调用生产的set
方法。一般是在get
的时候调用收集函数,set
的时候触发函数。toRefs和ObjectRefImpl
我们在使用
reactive
时通过缓存属性值很可能会失去响应式特性。因为属性值可能是reactive
不支持深入响应的值,这时候缓存属性值,或者是通过ES6
解构出来的值是不具备响应特性的。比如在下面这两种使用方式:这样的话就得一直使用
reuser.name
的方式来进行访问,vue
有两个api
能很好的解决这个问题,就是toRef
和toRefs
。toRef
通过reactive
代理和代理的某个属性生成为ref
并且可以携带默认值。而toRefs
根据reactive
代理生成所有属性值为ref
的对象。生成的ref
的value
是代理属性值的映射,两端更改都会实时同步,我们看看是如何使用的:接下来我们看看
toRef
和toRefs
的具体实现:toRef
会先查看proxy[key]
是否是ref
如果是的话直接返回,如果不是则创建ObjectRefImpl
并且将参数传入,ObjectRefImpl
会标识当前对象是ref
类型 (通过__v_isRef
属性) ,并且缓存proxy
、key
和默认值。get value
时直接通过proxy[key]
来获取并返回,如果回去的值是undefined
则使用默认值。set value
时则通过proxy[key] = newVal
来设置。toRefs
则是将每个属性都调用一次没有默认值的toRef
,并且返回与proxy
一致的数据结构。为什么这里的
ObjectRefImpl
类不需要dep
属性和收集依赖和触发更改呢?这是因为_object
属性本身是proxy
类型,当我们在使用proxy[key]
就实现了收集依赖,在proxy[key] = newVal
是就触发了更改。其他辅助方法
ref
文件中还声明了其他辅助方法,比如triggerRef
手动触发ref
的更改使关联的effect
重新执行收集函数;unref
获取ref
的原始值。这两个方法比较简单直接看源码即可,这里就不再讲解了。还有一个辅助方法
proxyRefs
,这个方法将一个对象直属属性内的所有ref
属性值解构访问 (不需要通过value
下标访问) 。什么是直属属性就是第一层属性,比如下方的代码:proxyRefs
只对第一层属性的ref
解构。我们看看它的源码:当
proxyRefs
入参是reactive
对象时则直接返回,reactive
对象本身会对ref
解构,而且是深度的,这里就不需要处理。有个特殊情况shallowReactive
对象不会对ref
解构,但是也直接返回了,也就是说这个方法对shallowReactive
对象时无效的!如果
proxyRefs
入参不是reactive
对象,则创建代理,get
拦截器通过unref
来获取值返回,set
拦截器通过判断当前要更新的是否是ref
如果是则更新value
。到这里我们
ref
的所有内容就已经讲完了,接下来日常小结。小结
ref
对象自身附加了dep
,在收集依赖时通过trackEffects
函数,触发时通过triggerEffects
函数ref
能够创建深度响应式是依赖了reactive
Proxy
代理对象可以通过toRef
和toRefs
辅助方法保持对单个属性的引用,赋值修改会映射到Proxy
customRef
函数可以创建自由度极高的响应式对象上一章:vue3-effect源码解析
The text was updated successfully, but these errors were encountered: