从 Vue3.x 源码上深入理解响应式原理
什么是数据驱动视图?
了解 Vue 的小伙伴都知道,Vue 中有一种数据驱动视图
的概念
拿一段简单的代码说明一下,例如
<template>
<span>{{text}}</span>
</template>
<script setup>
import { ref } from 'vue'
const text = ref(1)
setTimeout(() => {
text.value = 2
}, 1000)
</script>
该效果:从 1 变为 2
而我自始至终只是改变text
的值,浏览器渲染出来的视图就跟着变化,这就是数据驱动视图
什么是响应式?
那 Vue 中,是如何实现的呢?
就是响应式
那什么是响应式
呢?
以上述例子说明,Vue 在内部封装一个渲染方法 render,初始化时调用 render,而由于 text 初始值 为 1 ,所以渲染出来就是 1 ,只不过 setTimeout 定时器里面的函数把 text 的值变为了 2,此时 Vue 就是再次调用 render 更新成了 2
其,改变 text 值从1 变为 2,触发了响应式
,刷新了视图
反过来说,由于 text 是一个具有响应式
的对象,所以更新 text 的值才能触发视图更新
如何实现响应式?
那响应式在 Vue 是如何实现的呢?
以 ref 来说明一下(本文基于 Vue 3.2.47 进行分析)
收集依赖
初始化
从调用堆栈(Call Stack)能看出来应该是符合预期的
其中关键代码:ref -> createRef -> new RefImpl
// 代码在 /core-3.2.47/packages/reactivity/src/ref.ts 第 82 行
export function ref(value?: unknown) {
return createRef(value, false)
}
// 代码在 /core-3.2.47/packages/reactivity/src/ref.ts 第 99 行
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// 代码在 /core-3.2.47/packages/reactivity/src/ref.ts 第 106 行
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) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
mountComponent
执行完 setupComponent
后将会执行 setupRenderEffect
// 代码在 /core-3.2.47/packages/runtime-core/src/renderer.ts 第 1182 行
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 省略部分代码...
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance)
if (__DEV__) {
endMeasure(instance, `init`)
}
}
// 省略部分代码...
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
关键是:如何生成副作用代码,添加到响应式数据中?
// 代码在 /core-3.2.47/packages/runtime-core/src/renderer.ts 第 1292 行
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// 省略部分代码...
// 代码在 /core-3.2.47/packages/runtime-core/src/renderer.ts 第 1545 行
// 创建响应式数据和更新视图的依赖关系
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
instance.scope // track it in component's effect scope
))
const update: SchedulerJob = (instance.update = () => effect.run())
// 省略部分代码...
}
关键就是使用 ReactiveEffect
和 activeEffect
只需要关注两点即可:
ReactiveEffect
成员变量deps
(收集依赖)、fn
(副作用函数)、scheduler
(调度器)ReactiveEffect
中的run
方法会将this
赋值给activeEffect
// 代码在 /core-3.2.47/packages/reactivity/src/effect.ts 第 48 行
export let activeEffect: ReactiveEffect | undefined
// 代码在 /core-3.2.47/packages/reactivity/src/effect.ts 第 53 行
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
/**
* @internal
*/
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
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
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
之后就是主动触发 get value
trackRefValue -> trackEffects
其中由 createDep
生成 set
传入 trackEffects
此时的 set
赋值给 RefImpl
的 dep
将activeEffect
( ReactiveEffect 的 this )添加到 Set
中
ps:此时的 dep
是 set
类型
// 代码在 /core-3.2.47/packages/reactivity/src/ref.ts 第 40 行
export 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()))
}
}
}
// 代码在 /core-3.2.47/packages/reactivity/src/dep.ts 第 21 行
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
// 代码在 /core-3.2.47/packages/reactivity/src/effect.ts 第 232 行
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}
好了,这就是收集依赖
的过程。
派发更新
派发更新
,相对与收集依赖
会简单很多
当定时器 setTimeout
中的 text.value = 2
时,将会 set value
// 代码在 /core-3.2.47/packages/reactivity/src/effect.ts 第 347 行
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
// 代码在 /core-3.2.47/packages/reactivity/src/effect.ts 第 365 行
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
triggerEffects
触发的便是 RefImpl
中的 dep
,即 ReactiveEffect
set 集合
当 ReactiveEffect
中有调度器 scheduler
就优先执行调度器,没有就使用 run
方法(运行fn
)
总结
所谓的响应式
,说白了,就是先进行收集
,收集当前的数据会影响什么方法,或者说收集当前的数据需要再次触发什么方法?当数据改变时进行派发
,将之前的收集的方法运行一遍即可。
同理,在Vue中,他把改变视图的方法作为响应式数据对象的副作用
,当改变数据时会触发改变视图的方法
感谢阅读
转载自:https://juejin.cn/post/7202623084276744251