theme: fancy
一. Ref 用法
这是 ref 最基本的用法,返回来的count是一个响应式的代理值
const count = ref(0)
二. 实现
1. ref 函数
我们调用的ref函数,传进来一个 val 值,调用 createRef 函数,我们来看下该函数的实现
源码路径:packages/reactivity/src/ref.ts
function ref(value?: unknown) {
return createRef(value, false)
}
2. createRef 函数
该函数里边做了一个判断,该值如果已经是一个Ref,则直接返回,否则创建一个 Ref 实例,让我们看下 RefImpl 类的实现
源码路径:packages/reactivity/src/ref.ts
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
3. RefImpl 类的实现
定义了四个变量
_value
: 用来保存加工后实现响应化的值_rawValue
: 用来保存当前未经加工过的值dep
: 用来收集依赖,是一个 Set类型_v_isRef
: 用来标识该值是否经过 ref 加工
在constructor中,我们为_rawValue 和_value实现了初始化,分别调用了toRaw和toReactive函数,toReactive将 object类型转换为响应式,所以ref中也可以实现对象的响应式管理,我们将他放在下篇文章中来讲,它是实现对象响应式的主要方法,我们今天只讲基本类型。
源码路径:packages/reactivity/src/ref.ts
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)
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)
}
}
}
4. trackRefValue 依赖收集
从上边代码中我们可以看到,我们使用 get 和 set对 value 属性实现了拦截,其实如同Object.defineProperty,在 get中实现依赖收集,set中通知依赖更新,而 vue3中是通过调用trackRefValue来实现跟踪依赖。
在该方法中,调用了trackEffect方法,在收集第一个依赖时执行createDep方法来作为参数传入。
我们接着往下看。
源码路径:packages/reactivity/src/ref.ts
function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
5. createDep 创建依赖容器
其实就是创建了一个 Set 类型,之后我们进入到下一步 trackEffects。
源码路径:packages/reactivity/src/dep.ts
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
6. trackEffects
可以看到这里我们将activeEffect 放入的我们的Ref的dep中,也就是Set类型,那这个activeEffect是什么呢?
它其实就是我们的componentUpdateFn,每次更改value值时,通知对应的组件更新。我们来看下activeEffect是如何赋值的。
源码路径:packages/reactivity/src/effect.ts
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (shouldTrack) {
// 放入我们的依赖集合
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
}
三. activeEffect 的赋值
我们在上边说过,activeEffect其实就是componentUpdateFn函数,所以该值应该是一个变化的值,它是如何准确无误的将每个组件更新函数来放入到对应的dep中的呢,我们回到setupRenderEffect函数里边来看一下。
//源码路径 core/packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => { }
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
))
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)
update()
}
从上边源码中我们可以看到,在setRenderEffectFn方法中实现了componentUpdateFn的定义,同时在此时创建了一个ReactiveEfect的对象,同时调用了ReactiveEffect中的run方法,并且使用bind来改变其中的this指向该effect。我们来进一步看下run中发生了什么。
源码路径:packages/reactivity/src/effect.ts
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
从上边代码中我们看到,在try中,我们先将this赋值给activeEffect,然后调用传入的componentUpdateFn函数,在该函数中会获取我们定义的ref变量的值,触发get,然后将该this也就是effect保存到该Ref的dep中,这就完成了我们的整个依赖收集的过程。
四. 依赖收集
收集依赖后,下一步就是在数据改变之后通知依赖更新,我们来看下set中是如何做的。
在set中我们调用了triggerRefValue方法,传入了this,也就是当前Ref实例,还有新的值。
源码路径:packages/reactivity/src/ref.ts
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)
}
}
1. triggerRefValue
该方法中调用了triggerEffects方法,将ref.dep也就是之前收集依赖的Set,传入。让我们接着往下看。
// 源码路径:packages/reactivity/src/ref.ts
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)
}
}
}
2. triggerEffects
该方法中,遍历Set,然后调用每个依赖上的run方法,也就是执行更新函数,进而使页面刷新。实现数据到页面的响应式渲染。
// 源码路径:packages/reactivity/src/effect.ts
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
五. 总结
vue3 中对Ref的实现基本没有太大改变,也是利用setter和getter对数据实现数据劫持,而Reactive的响应式原理就与vue2的方案截然不同了。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。