跳至主要內容

6. Computed 实现原理


6. Computed 实现原理

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

  • 计算属性的 getter 只有当取值时才会执行。
  • 计算属性是具备缓存的,如果依赖的值不发生变化,不会重新执行 getter。
  • 计算属性也是一个 effect,内部也具备依赖收集的功能。
import { reactive, effect, computed } from './reactivity.js';
const state = reactive({ flag: true, name: 'erxiao', age: 30 });
const aliasName = computed((oldValue) => {
	console.log('computed-run', oldValue);
	return '**' + state.name + '**';
});
const runner = effect(() => {
	console.log('effect-run');
	console.log(aliasName.value);
	console.log(aliasName.value);
	console.log(aliasName.value);
});
setTimeout(() => {
	state.name = 'Handsome erxiao';
}, 1000);

1.增加 dirty 标识

// 将常量统一维护在一起
export enum ReactiveFlags {
	IS_REACTIVE = '__v_isReactive' // 基本上唯一
}

// dirty 等级
export enum DirtyLevels {
	NotDirty = 0, // 不是脏值
	Dirty = 4 // 脏值
}

export class ReactiveEffect {
	_depsLength = 0; // 用于记录依赖的个数
	_trackId = 0; // 用于记录收集的次数
	_runnings = 0;
	deps = [];
	_dirtyLevel = DirtyLevels.Dirty;

	public get dirty() {
		// 是否是脏值
		return this._dirtyLevel === DirtyLevels.Dirty;
	}

	public set dirty(v) {
		this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty;
	}

	run() {
		this._dirtyLevel = DirtyLevels.NotDirty; // 运行一次后,脏值变为不脏
		// ...
	}
}

2.计算属性实现

import { isFunction } from '@vue/shared';
import {
	activeEffect,
	ReactiveEffect,
	trackEffects,
	triggerEffects
} from './effect';

class ComputedRefImpl {
	public effect;
	public _value;
	public dep;
	constructor(getter, public setter) {
		this.effect = new ReactiveEffect(
			() => getter(this._value), // 计算属性依赖的值会对计算属性effect进行收集
			() => triggerRefValue(this) // 计算属性依赖的值变化后会触发此函数
		);
	}
	get value() {
		// 脏值,并且值不相同才出发更新
		if (
			this.effect.dirty &&
			!Object.is(this._value, (this._value = this.effect.run()))
		) {
			trackRefValue(this); // 取值时进行依赖收集
		}
		return this._value;
	}
	set value(newValue) {
		this.setter(newValue);
	}
}
export function computed(getterOrOptions) {
	const onlyGetter = isFunction(getterOrOptions); // 传入的是函数就是getter
	let getter;
	let setter;
	if (onlyGetter) {
		getter = getterOrOptions;
		setter = () => {};
	} else {
		getter = getterOrOptions.get;
		setter = getterOrOptions.set;
	}
	// 创建计算属性
	return new ComputedRefImpl(getter, setter);
}

创建 ReactiveEffect 时,传入trigger函数,稍后依赖的属性变化时调用此方法!(计算属性更新时只需要更新 dirty),同时将scheduler参数作为第三个参数

const _effect = new ReactiveEffect(
	fn,
	() => {}, // trigger 函数
	() => {
		// scheduler
		_effect.run();
	}
);

export function triggerEffects(dep) {
	for (const effect of dep.keys()) {
		// 计算属性,则将dirty变为true在
		if (effect._dirtyLevel < DirtyLevels.Dirty) {
			effect._dirtyLevel = DirtyLevels.Dirty;
			// 需要差异化开,计算属性只需要修改dirty
			effect.trigger();
		}
		if (!effect._runnings) {
			// 如果正在运行什么都不做
			if (effect.scheduler) {
				effect.scheduler();
			}
		}
	}
}