「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。
前言
前篇 Vue3.0源码学习——响应式原理(一) 和
Vue3.0源码学习——响应式原理(二) 通过reactive
源码的学习和单步调试了解了Vue3.0 响应式的原理,内部是使用了 Proxy
的 getter 去收集数据的依赖 dep
,这个依赖其实就是组件的更新函数 activeEffect
,然后在 setter
中去触发依赖中的组件更新函数,从而达到了响应式的效果,这一篇将学习另外一个响应式API ref
的源码
ref
ref文档 ref
和 reactive
都可以将数据转为响应式,区别在于 ref
接受一个内部值并返回一个响对象,reactive
返回对象的响应式副本,因此一般在 ref
中使用单值,reactive
中使用对象,但是 ref
中也可以使用对象
源码分析
这里使用 Vscode 查看源码,首先定位
ref
函数的位置,ctrl + t 查找 ref()
ref
函数位置packages\reactivity\src\ref.ts
可以看到有多个ref
函数的类型定义
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
- 因此在平时开发中如果使用 typeScript 以下两种方式都可以约束类型
const numberData = ref<number>(1)
const stringData: Ref<string> = ref('')
ref
函数中返回了一个createRef
函数把我们定义的数据传入,并加上了第二个参数false
export function ref(value?: unknown) {
return createRef(value, false)
}
createRef
函数中返回了一个RefImpl
类的实例,并将自身的参数原封不动传入
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
RefImpl
如果constructor
的第二个参数shallow
是true
就不转为响应式数据,之前ref
传入的第二个参数显然是false
因此会转为响应式数据toReactive
,然后对传入的value
进行get
和set
的操作,因此在使用ref
时需要使用.vaule
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
// 深层次用toReactive递归转为reactive
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)
}
}
}
- 这里可以看一下
toReactive
函数,会判断ref
中传入的值的类型,如果是对象类型就会调用reactive
函数,因此ref
中也可以使用对象类型的数据
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
get
会在返回数据前,包一层trackRefValue
,其中关键就是对数据依赖dep
的收集,然后触发trackEffects
跟踪依赖
export function trackRefValue(ref: RefBase<any>) {
if (isTracking()) {
ref = toRaw(ref)
// 为Ref实例创建一个依赖
if (!ref.dep) {
// 一个由RectiveEffect构成的Set
ref.dep = createDep()
}
if (__DEV__) {
trackEffects(ref.dep, {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
// 依赖收集
trackEffects(ref.dep)
}
}
}
set
会在设置新值的时候执行triggerRefValue
函数,并且触发副作用函数triggerEffects
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
if (__DEV__) {
triggerEffects(ref.dep, {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal
})
} else {
triggerEffects(ref.dep)
}
}
}
- 到这里就知道了
get
和set
做的事情和reactive
是一样的了