vue3响应式原理:可被收集/也可被收集的computed
计算属性指的是,数据是由其他数据的复杂计算所得。以下是computed
相关的依赖收集和派发更新的实现原理:
先看个例子:
<template>
<div> {{ doubleCount }} </div>
<button @click="increaseCount">increaseCount</button>
</template>
<script setup>
import { ref, computed } from 'vue'
// 定义响应式
const count = ref(0)
// 定义计算属性
const doubleCount = computed(() => {
return count.value * 3
})
// 修改数据:引起计算属性变化,再引起视图变化
const increaseCount = () => {
count.value++;
}
</script>
一、ref
// ref函数
function ref(value) {
return createRef(value, false);
}
// createRef函数
function createRef(rawValue, shallow) {
// 如果是ref,直接返回
if (isRef(rawValue)) {
return rawValue;
}
// 创建RefImpl实例
return new RefImpl(rawValue, shallow);
}
// 定义RefImpl类(包含get和set函数)
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
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) {
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, 4, newVal);
}
}
}
以上是响应式 apiref
的基本逻辑,最终是实例化了RefImpl
,当访问到该数据时,会通过trackRefValue
来实现依赖收集,当修改其数据时,又会通过triggerRefValue
的方式进行派发更新。
二、computed
// 定义computed入口
const computed = (getterOrOptions, debugOptions) => {
const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
if (!!(process.env.NODE_ENV !== "production")) {
const i = getCurrentInstance();
if (i && i.appContext.config.warnRecursiveComputed) {
c._warnRecursive = true;
}
}
return c;
};
// 这个computed就是以上的computed$1
function computed(getterOrOptions, debugOptions, isSSR = false) {
let getter;
let setter;
const onlyGetter = isFunction(getterOrOptions);
if (onlyGetter) {
getter = getterOrOptions;
setter = !!(process.env.NODE_ENV !== "production")
? () => {
warn("Write operation failed: computed value is readonly");
}
: NOOP;
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
const cRef = new ComputedRefImpl(
getter,
setter,
onlyGetter || !setter,
isSSR
);
if (!!(process.env.NODE_ENV !== "production") && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack;
cRef.effect.onTrigger = debugOptions.onTrigger;
}
return cRef;
}
// 定义ComputedRefImpl类(包含get和set函数)
class ComputedRefImpl {
constructor(getter, _setter, isReadonly, isSSR) {
this.getter = getter;
this._setter = _setter;
this.dep = void 0;
this.__v_isRef = true;
this["__v_isReadonly"] = false;
// 创建ReactiveEffect实例
this.effect = new ReactiveEffect(
() => getter(this._value),
() => triggerRefValue(this, this.effect._dirtyLevel === 2 ? 2 : 3)
);
this.effect.computed = this;
this.effect.active = this._cacheable = !isSSR;
this["__v_isReadonly"] = isReadonly;
}
get value() {
const self = toRaw(this);
if (
(!self._cacheable || self.effect.dirty) &&
hasChanged(self._value, (self._value = self.effect.run()))
) {
triggerRefValue(self, 4);
}
trackRefValue(self);
if (self.effect._dirtyLevel >= 2) {
if (!!(process.env.NODE_ENV !== "production") && this._warnRecursive) {
warn(COMPUTED_SIDE_EFFECT_WARN, `getter: `, this.getter);
}
triggerRefValue(self, 2);
}
return self._value;
}
set value(newValue) {
this._setter(newValue);
}
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
get _dirty() {
return this.effect.dirty;
}
set _dirty(v) {
this.effect.dirty = v;
}
}
以上是计算属性 apicomputed
的基本逻辑,最终是实例化了ComputedRefImpl
,当访问到该计算属性时,会通过trackRefValue
来实现依赖收集,当其依赖的数据变化时,又会通过triggerRefValue
的方式进行派发更新。
三、依赖收集流程
创造类:ReactiveEffect
中的 run
为:
run() {
this._dirtyLevel = 0;
if (!this.active) {
return this.fn();
}
let lastShouldTrack = shouldTrack;
let lastEffect = activeEffect;
try {
shouldTrack = true;
activeEffect = this;
this._runnings++;
preCleanupEffect(this);
return this.fn();
} finally {
postCleanupEffect(this);
this._runnings--;
activeEffect = lastEffect;
shouldTrack = lastShouldTrack;
}
}
阶段 1:渲染 effect
当执行到setupRenderEffect
逻辑时,执行流程为: update()
--> effect.run()
--> this.run()
。
重点关注activeEffect = this
,这里就将渲染的effect
作为当前激活状态的activeEffect
。
这里执行的this.fn()
就是渲染组件的逻辑componentUpdateFn
,其中由const subTree = instance.subTree = renderComponentRoot(instance)
获取vnode
,由patch
进行渲染。
subTree
中有主要逻辑:
result = normalizeVNode(
render.call(thisProxy, proxyToUse, renderCache, props, setupState, data, ctx)
);
执行render
函数,就会获取例子中的doubleCount
,进而执行其get
函数中的逻辑self._value = self.effect.run()
。
阶段 2:计算属性 effect
重点关注activeEffect = this
,这里就将计算属性的effect
作为当前激活状态的activeEffect
。
这里执行的this.fn()
就是计算属性的逻辑() => getter(this._value)
,即() => { return count.value * 3 }
,访问到了count.value
,会触发RefImpl
对应的get
函数get value() { trackRefValue(this); return this._value; }
。其中rackRefValue(this)
就是依赖收集的逻辑:
function trackRefValue(ref2) {
var _a;
if (shouldTrack && activeEffect) {
ref2 = toRaw(ref2);
trackEffect(
activeEffect,
(_a = ref2.dep) != null
? _a
: (ref2.dep = createDep(
() => (ref2.dep = void 0),
ref2 instanceof ComputedRefImpl ? ref2 : void 0
)),
!!(process.env.NODE_ENV !== "production")
? {
target: ref2,
type: "get",
key: "value",
}
: void 0
);
}
}
function trackEffect(effect2, dep, debuggerEventExtraInfo) {
var _a;
if (dep.get(effect2) !== effect2._trackId) {
dep.set(effect2, effect2._trackId);
const oldDep = effect2.deps[effect2._depsLength];
if (oldDep !== dep) {
if (oldDep) {
cleanupDepEffect(oldDep, effect2);
}
effect2.deps[effect2._depsLength++] = dep;
} else {
effect2._depsLength++;
}
if (!!(process.env.NODE_ENV !== "production")) {
(_a = effect2.onTrack) == null
? void 0
: _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));
}
}
}
trackRefValue
中将activeEffect
和ref2.dep
作为参数传入。
trackEffect
中将当前激活的effect2
作为key
,effect2._trackId
作为value
收集到dep
中。同时,通过effect2.deps[effect2._depsLength++] = dep
的方式将dep
记录到deps
中去。
至此,响应式数据ref
和计算属性computed
之间的关系完成建立。
在run
逻辑执行结束后,通过activeEffect = lastEffect
将当前激活的effect
赋值为渲染effect
。
阶段 3:渲染 effect
继续接着阶段 1 中的:
if (
(!self._cacheable || self.effect.dirty) &&
hasChanged(self._value, (self._value = self.effect.run()))
) {
triggerRefValue(self, 4);
}
trackRefValue(self);
执行到依赖收集方法trackRefValue(self)
,也就是阶段 2 中的trackRefValue
和trackEffect
,这个阶段中,计算属性数据ComputedRefImpl
和渲染effect
之间的关系完成建立。
等run
逻辑执行结束后,通过activeEffect = lastEffect
将当前激活的effect
赋值为undefined
。
收集过程小结:subtree
获取过程中触发计算属性doubleCount
的求值 --> computed
中的get
--> 触发() => { count.value * 3}
--> 触发count.value
的依赖收集,收集的是computed
计算属性 --> 触发trackRefValue(self)
的执行,触发的是computed
收集渲染effect
。
四、派发更新
当我们点击例子中的按钮,触发数据变化时,会让视图重新渲染。
const increaseCount = () => {
count.value++;
};
修改count.value
会触发RefImpl
的get
函数:
function set(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, 4, newVal);
}
}
function triggerRefValue(ref2, dirtyLevel = 4, newVal) {
ref2 = toRaw(ref2);
const dep = ref2.dep;
// 如果有收集到的dep,执行triggerEffects
if (dep) {
triggerEffects(
dep,
dirtyLevel,
!!(process.env.NODE_ENV !== "production")
? {
target: ref2,
type: "set",
key: "value",
newValue: newVal,
}
: void 0
);
}
}
function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {
var _a;
pauseScheduling();
for (const effect2 of dep.keys()) {
let tracking;
if (
effect2._dirtyLevel < dirtyLevel &&
(tracking != null
? tracking
: (tracking = dep.get(effect2) === effect2._trackId))
) {
effect2._shouldSchedule ||
(effect2._shouldSchedule = effect2._dirtyLevel === 0);
effect2._dirtyLevel = dirtyLevel;
}
if (
effect2._shouldSchedule &&
(tracking != null
? tracking
: (tracking = dep.get(effect2) === effect2._trackId))
) {
if (!!(process.env.NODE_ENV !== "production")) {
(_a = effect2.onTrigger) == null
? void 0
: _a.call(
effect2,
extend({ effect: effect2 }, debuggerEventExtraInfo)
);
}
// 触发effet2的派发更新
effect2.trigger();
if (
(!effect2._runnings || effect2.allowRecurse) &&
effect2._dirtyLevel !== 2
) {
effect2._shouldSchedule = false;
if (effect2.scheduler) {
// 如果有计划中的函数scheduler,推入到queueEffectSchedulers中去
queueEffectSchedulers.push(effect2.scheduler);
}
}
}
}
resetScheduling();
}
// 执行完事件派发更新后
function resetScheduling() {
pauseScheduleStack--;
while (!pauseScheduleStack && queueEffectSchedulers.length) {
queueEffectSchedulers.shift()();
}
}
最终会执行到effect2.trigger()
,也就是() => triggerRefValue(this, this.effect._dirtyLevel === 2 ? 2 : 3)
。
下一个阶段的triggerRefValue
会触发effect2.trigger()
,也就是noop = () => {}
,最终会执行queueEffectSchedulers.push(effect2.scheduler)
。
再接着会执行到queueEffectSchedulers.shift()()
,这里开始就是视图更新的逻辑。
总结:首次渲染时,按照
渲染effect
-->ComputedRefImpl
-->RefImpl
的顺序进行依赖收集;数据更新时按照RefImpl
,computed
和渲染effect
顺序进行派发更新,完成视图的更新。
转载自:https://juejin.cn/post/7390902948614963212