响应式系统
vue2
watcher,实现回调函数
export default class Watcher {
export default class Watcher {constructor(data, expOrFn, cb, options) {this.data = data;if (typeof expOrFn === "function") {this.getter = expOrFn;} else {this.getter = parsePath(expOrFn);}...this.cb = cb;this.value = this.get(); // 回调函数要用到}...
}
}
initWatch函数
// state.js
import Watcher from "./watcher";
import { pushTarget, popTarget } from "./dep";export function initWatch(data, watch) {for (const key in watch) {const handler = watch[key];createWatcher(data, key, handler);}
}function createWatcher(data, expOrFn, handler) {return $watch(data, expOrFn, handler);
}function $watch(data, expOrFn, handler) {new Watcher(data, expOrFn, handler);
}
immediate
import { observe } from "./reactive";
import { initWatch } from "./state";
const options = {data: {title: "liang",},watch: {title: {handler(newVal, oldVal) {console.log("收到变化", newVal, oldVal);},immediate: true,},},
};
observe(options.data);
initWatch(options.data, options.watch);options.data.title = "changeTitle";
/*** Get the raw type string of a value, e.g., [object Object].*/
const _toString = Object.prototype.toString;/*** Strict object type check. Only returns true* for plain JavaScript objects.*/
export function isPlainObject(obj) {return _toString.call(obj) === "[object Object]";
}function createWatcher(data, expOrFn, handler, options) {// 如果是对象,就将 handler 和 options 分离if (isPlainObject(handler)) {options = handler;handler = handler.handler;}return $watch(data, expOrFn, handler, options);
}
function $watch(data, expOrFn, handler, options) {/******新增 options*************************/const watcher = new Watcher(data, expOrFn, handler, options);/************************************/if (options.immediate) {handler.call(data, watcher.value);}return function unwatchFn() {watcher.teardown();};
}
computed
- 惰性的响应式数据
- 处理computed的值
- computed属性的响应式
添加lazy属性和dirty属性,dirty为true表示watcher依赖的属性发生了变化,需要重新求值。dirty为false表示依赖的属性没有发生变化,无需重新求值
export default class Watcher {constructor(data, expOrFn, cb, options) {this.data = data;if (typeof expOrFn === "function") {this.getter = expOrFn;} else {this.getter = parsePath(expOrFn);}...// optionsif (options) {this.deep = !!options.deep;this.sync = !!options.sync;this.lazy = !!options.lazy;}this.dirty = this.lazy;this.value = this.lazy ? undefined : this.get();}/*** Evaluate the getter, and re-collect dependencies.*/get() {pushTarget(this); // 保存包装了当前正在执行的函数的 Watcherlet value;try {value = this.getter.call(this.data, this.data);} catch (e) {throw e;} finally {// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {traverse(value);}popTarget();this.cleanupDeps();}return value;}.../*** Evaluate the value of the watcher.* This only gets called for lazy watchers.*//******新增 *************************/evaluate() {this.value = this.get();this.dirty = false; // dirty 为 false 表示当前值已经是最新}/**********************************/update() {/******新增 *************************/if (this.lazy) {this.dirty = true;/************************************/} else if (this.sync) {this.run();} else {queueWatcher(this);}}}
输出value之前会执行一次evaluate。
处理computed的值
export function noop(a, b, c) {}const computedWatcherOptions = { lazy: true };// computed properties are just getters during SSR
export function initComputed(data, computed) {const watchers = (data._computedWatchers = Object.create(null)); // 保存当前所有的 watcher,并且挂在 data 上供后边使用for (const key in computed) {const userDef = computed[key];const getter = typeof userDef === "function" ? userDef : userDef.get; // 如果是对象就取 get 的值// create internal watcher for the computed property.watchers[key] = new Watcher(data,getter || noop,noop,computedWatcherOptions);// component-defined computed properties are already defined on the// component prototype. We only need to define computed properties defined// at instantiation here.defineComputed(data, key, userDef);}
}
defineComputed 是将computed函数定义为data的属性,就可以和正常属性一样使用computed
const sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop,
};
export function defineComputed(target, key, userDef) {// 初始化 get 和 setif (typeof userDef === "function") {sharedPropertyDefinition.get = createComputedGetter(key);sharedPropertyDefinition.set = noop;} else {sharedPropertyDefinition.get = userDef.get? createComputedGetter(key): noop;sharedPropertyDefinition.set = userDef.set || noop;}// 将当前属性挂到 data 上Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key]; // 拿到相应的 watcherif (watcher) {if (watcher.dirty) {watcher.evaluate();}return watcher.value;}};
}
computed属性的响应式
export default class Watcher {constructor(data, expOrFn, cb, options) {this.data = data;if (typeof expOrFn === "function") {this.getter = expOrFn;} else {this.getter = parsePath(expOrFn);}this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 idthis.deps = [];this.newDeps = []; // 记录新一次的依赖this.newDepIds = new Set();...this.dirty = this.lazy;this.value = this.lazy ? undefined : this.get();}/*** Add a dependency to this directive.*/addDep(dep) {const id = dep.id;// 新的依赖已经存在的话,同样不需要继续保存if (!this.newDepIds.has(id)) {this.newDepIds.add(id);this.newDeps.push(dep);if (!this.depIds.has(id)) {dep.addSub(this);}}}/*** Evaluate the value of the watcher.* This only gets called for lazy watchers.*/evaluate() {this.value = this.get();this.dirty = false;}/******新增 *************************//*** Depend on all deps collected by this watcher.*/depend() {let i = this.deps.length;while (i--) {this.deps[i].depend();}}/************************************/
}
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key];if (watcher) {if (watcher.dirty) {watcher.evaluate();}if (Dep.target) {watcher.depend();}return watcher.value;}};
}
vue3
vue3中的nextick放到后面渲染的时候讲。
export function effect<T = any>(fn: () => T,options?: ReactiveEffectOptions,
): ReactiveEffectRunner {// fn嵌套了effect => effect(() => {effect(fn)})if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {fn = (fn as ReactiveEffectRunner).effect.fn}// 实例化ReactiveEffectconst _effect = new ReactiveEffect(fn, NOOP, () => {if (_effect.dirty) {_effect.run()}})if (options) {// 把option浅拷贝到_effect身上extend(_effect, options)// 记录effect作用域if (options.scope) recordEffectScope(_effect, options.scope)}if (!options || !options.lazy) {// 立即执行effect.run()// 这就是定义一个effect传入的副作用函数会立即执行_effect.run()}// 通过bind改变_effect的this指向,返回函数const runner = _effect.run.bind(_effect) as ReactiveEffectRunner// runnner的effect赋值为_effectrunner.effect = _effectreturn runner
}
export class ReactiveEffect<T = any> {active = true // 标记为激活状态deps: Dep[] = [] // 保存了该reactiveEffect所依赖的所有响应式对象的dep对象computed?: ComputedRefImpl<T>allowRecurse?: booleanonStop?: () => voidonTrack?: (event: DebuggerEvent) => voidonTrigger?: (event: DebuggerEvent) => void_dirtyLevel = DirtyLevels.Dirty_trackId = 0_runnings = 0_shouldSchedule = false_depsLength = 0constructor(public fn: () => T,public trigger: () => void,public scheduler?: EffectScheduler,scope?: EffectScope, // 这个在组件那块用) {// 该副作用所属的作用域recordEffectScope(this, scope)}// run函数: lazy为false直接调的_effect.runrun() {this._dirtyLevel = DirtyLevels.NotDirty// 非激活的情况下只需要执行函数,不需要收集依赖。if (!this.active) {return this.fn()}// shouldTrack为全局变量,当前副作用是否需要被追踪let lastShouldTrack = shouldTracklet lastEffect = activeEffecttry {shouldTrack = trueactiveEffect = thisthis._runnings++preCleanupEffect(this)return this.fn()} finally {postCleanupEffect(this)this._runnings--activeEffect = lastEffectshouldTrack = lastShouldTrack}}stop() {if (this.active) {preCleanupEffect(this)postCleanupEffect(this)this.onStop && this.onStop()this.active = false}}
}
上面的代码是effect的核心。
接下来看watch的实现,首先看一下使用
watch(x, (newValue, oldValue) => {console.log(`x is ${newValue}`)
})
watch(() => {() => x.value + y.value(newValue, oldValue) => {console.log(`sum of x + y is: ${newValue}`)}
})
watch([x, () => y.value], ([newX, newY]) => {console.log(`x is ${newX} and y is ${newY}`)
})
第一个参数: ref(包括计算属性) 响应式对象 getter函数 多个数据源组成的数组
第二个参数: 发生变化的时候的回调函数,接受三个参数,新值,旧值,以及用来注册副作用清理的回调函数
第三个参数: immediate: 创建的时候立即触发回调。deep: 在深层级变更的时候触发回调。flush: 调整回调函数的刷新时机。onTrack/onTrigger:调试侦听器的依赖。
watch本质上是利用了副作用函数重新执行时的可调度性,一个watch本身会创建一个effect,当这个effect依赖的响应式数据发生变化的时候,会执行该effect的调度函数,即scheduler
export function watch<T = any, Immediate extends Readonly<boolean> = false>(source: T | WatchSource<T>,cb: any,options?: WatchOptions<Immediate>,
): WatchStopHandle {if (__DEV__ && !isFunction(cb)) {warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +`supports \`watch(source, cb, options?) signature.`,)}return doWatch(source as any, cb, options)
}
export function watch<T = any, Immediate extends Readonly<boolean> = false>(source: T | WatchSource<T>,cb: any,options?: WatchOptions<Immediate>,
): WatchStopHandle {if (__DEV__ && !isFunction(cb)) {warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +`supports \`watch(source, cb, options?) signature.`,)}return doWatch(source as any, cb, options)
}
function doWatch(source: WatchSource | WatchSource[] | WatchEffect | object,cb: WatchCallback | null,{immediate,deep,flush,once,onTrack,onTrigger,}: WatchOptions = EMPTY_OBJ,
): WatchStopHandle { if (cb && once) {const _cb = cbcb = (...args) => {_cb(...args)unwatch()}}const instance = currentInstanceconst reactiveGetter = (source: object) =>deep === true? source // traverse will happen in wrapped getter below: // for deep: false, only traverse root-level propertiestraverse(source, deep === false ? 1 : undefined)let getter: () => anylet forceTrigger = false // 强制触发副作用函数执行let isMultiSource = false // 侦听的是否为多个源// 如果source是ref对象,则创建source.value的getter函数if (isRef(source)) {getter = () => source.value// 判断数据源是否为浅响应forceTrigger = isShallow(source)} else if (isReactive(source)) {getter = () => reactiveGetter(source)forceTrigger = true} else if (isArray(source)) {isMultiSource = trueforceTrigger = source.some(s => isReactive(s) || isShallow(s))getter = () =>source.map(s => {if (isRef(s)) {return s.value} else if (isReactive(s)) {return reactiveGetter(s)} else if (isFunction(s)) {return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)} else {__DEV__ && warnInvalidSource(s)}})} else if (isFunction(source)) {if (cb) {// 有传入,处理的是watch的情况getter = () =>callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)} else {// 没有传入,处理的是watcheffect的情况getter = () => {if (cleanup) {cleanup()}return callWithAsyncErrorHandling(source,instance,ErrorCodes.WATCH_CALLBACK,[onCleanup],)}}} else {getter = NOOP__DEV__ && warnInvalidSource(source)}// 2.x array mutation watch compatif (__COMPAT__ && cb && !deep) {const baseGetter = gettergetter = () => {const val = baseGetter()if (isArray(val) &&checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) {traverse(val)}return val}}// 处理的是watch的场景// 递归读取对象的属性值, 相应书数据,需要递归读取数据源中的每个属性if (cb && deep) {const baseGetter = gettergetter = () => traverse(baseGetter())}// 清除富国用函数let cleanup: (() => void) | undefinedlet onCleanup: OnCleanup = (fn: () => void) => {cleanup = effect.onStop = () => {callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)cleanup = effect.onStop = undefined}}let oldValue: any = isMultiSource? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE): INITIAL_WATCHER_VALUEconst job: SchedulerJob = () => {if (!effect.active || !effect.dirty) {return}if (cb) {// watch(source, cb)const newValue = effect.run()// 如果侦听的数据源是响应式数据,需要深度侦听,则deep为true// 如果需要强制触发副作用函数执行,则forceTrigger为true// 如果新旧值发生了变化if (deep ||forceTrigger ||(isMultiSource? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i])): hasChanged(newValue, oldValue)) ||(__COMPAT__ &&isArray(newValue) &&isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))) {// cleanup before running cb againif (cleanup) {cleanup()}// 传入这个函数,在执行完成之后更新新旧值callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [newValue,// pass undefined as the old value when it's changed for the first timeoldValue === INITIAL_WATCHER_VALUE? undefined: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE? []: oldValue,onCleanup,])oldValue = newValue}} else {// watchEffecteffect.run()}}// 让调度任务作为侦听器的回调,这样调度器就知道允许自己派发更新job.allowRecurse = !!cblet scheduler: EffectScheduler// sync 表示同步的watcherif (flush === 'sync') {scheduler = job as any // the scheduler function gets called directly} else if (flush === 'post') {// 放到微任务队列中,等待dom更行结束后执行scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)} else {// 调度器函数默认的执行方式,在组件更新之前执行,如果组件还没有挂载,则在组件挂载之前同步执行回调函数job.pre = trueif (instance) job.id = instance.uidscheduler = () => queueJob(job)}// 初始化getter函数和调度器函数scheduler后, 调用reactiveeffectconst effect = new ReactiveEffect(getter, NOOP, scheduler)const scope = getCurrentScope()const unwatch = () => {effect.stop()if (scope) {remove(scope.effects, effect)}}if (__DEV__) {effect.onTrack = onTrackeffect.onTrigger = onTrigger}// initial runif (cb) {if (immediate) {job()} else {oldValue = effect.run()}} else if (flush === 'post') {queuePostRenderEffect(effect.run.bind(effect),instance && instance.suspense,)} else {effect.run()}if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)return unwatch
}
export function traverse(value: unknown,depth = Infinity,seen?: Set<unknown>,
) {if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {return value}seen = seen || new Set()if (seen.has(value)) {return value}seen.add(value)depth--if (isRef(value)) {traverse(value.value, depth, seen)} else if (isArray(value)) {for (let i = 0; i < value.length; i++) {traverse(value[i], depth, seen)}} else if (isSet(value) || isMap(value)) {value.forEach((v: any) => {traverse(v, depth, seen)})} else if (isPlainObject(value)) {for (const key in value) {traverse(value[key], depth, seen)}for (const key of Object.getOwnPropertySymbols(value)) {if (Object.prototype.propertyIsEnumerable.call(value, key)) {traverse(value[key as any], depth, seen)}}}return value
}
原理类似于
function watch(souce,callBack,options = {}) {let getterif(typeof souce === 'function') {getter = souce}else {getter = ()=> traverse(souce)}let newVal,oldVal;let work = ()=>{//调用effectFn,得到新的newValnewVal = effectFn()//当响应式数据变化时候,会执行回调函数callBack()callBack(newVal,oldVal)//新值替换旧值,当作旧值。oldVal = newVal}//我们对传入的对象souce进行循环遍历,把所有属性进行监听const effectFn = effect(()=>getter(),{lazy:true,//lazy 是懒执行effectscheduler:work}) if(options.immediate) {work()}else{//我们自己触发点一次effect调用,它肯定优先effect的执行。因为只有当响应式数据变化时候,才会执行effectFn。这是我们手动执行,得到一个初试值oldVal。oldVal = effectFn()}}
export function triggerEffects(dep: Dep,dirtyLevel: DirtyLevels,debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {pauseScheduling()for (const effect of dep.keys()) {// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the resultlet tracking: boolean | undefinedif (effect._dirtyLevel < dirtyLevel &&(tracking ??= dep.get(effect) === effect._trackId)) {effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirtyeffect._dirtyLevel = dirtyLevel}if (effect._shouldSchedule &&(tracking ??= dep.get(effect) === effect._trackId)) {if (__DEV__) {// eslint-disable-next-line no-restricted-syntaxeffect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))}effect.trigger()if ((!effect._runnings || effect.allowRecurse) &&effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect) {effect._shouldSchedule = falseif (effect.scheduler) {queueEffectSchedulers.push(effect.scheduler)}}}}resetScheduling()
}
export function pauseScheduling() {pauseScheduleStack++
}
export function resetScheduling() {pauseScheduleStack--while (!pauseScheduleStack && queueEffectSchedulers.length) {queueEffectSchedulers.shift()!()}
}