响应式系统
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);
}
...
// options
if (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); // 保存包装了当前正在执行的函数的 Watcher
let 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 watching
if (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 和 set
if (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]; // 拿到相应的 watcher
if (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 函数可以判断是否存在某个 id
this.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
}
// 实例化ReactiveEffect
const _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赋值为_effect
runner.effect = _effect
return runner
}
export class ReactiveEffect<T = any> {
active = true // 标记为激活状态
deps: Dep[] = [] // 保存了该reactiveEffect所依赖的所有响应式对象的dep对象
computed?: ComputedRefImpl<T>
allowRecurse?: boolean
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
_dirtyLevel = DirtyLevels.Dirty
_trackId = 0
_runnings = 0
_shouldSchedule = false
_depsLength = 0
constructor(
public fn: () => T,
public trigger: () => void,
public scheduler?: EffectScheduler,
scope?: EffectScope, // 这个在组件那块用
) {
// 该副作用所属的作用域
recordEffectScope(this, scope)
}
// run函数: lazy为false直接调的_effect.run
run() {
this._dirtyLevel = DirtyLevels.NotDirty
// 非激活的情况下只需要执行函数,不需要收集依赖。
if (!this.active) {
return this.fn()
}
// shouldTrack为全局变量,当前副作用是否需要被追踪
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
}
}
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 = cb
cb = (...args) => {
_cb(...args)
unwatch()
}
}
const instance = currentInstance
const reactiveGetter = (source: object) =>
deep === true
? source // traverse will happen in wrapped getter below
: // for deep: false, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined)
let getter: () => any
let 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 = true
forceTrigger = 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 compat
if (__COMPAT__ && cb && !deep) {
const baseGetter = getter
getter = () => {
const val = baseGetter()
if (
isArray(val) &&
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
) {
traverse(val)
}
return val
}
}
// 处理的是watch的场景
// 递归读取对象的属性值, 相应书数据,需要递归读取数据源中的每个属性
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
// 清除富国用函数
let cleanup: (() => void) | undefined
let 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_VALUE
const 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 again
if (cleanup) {
cleanup()
}
// 传入这个函数,在执行完成之后更新新旧值
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup,
])
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
// 让调度任务作为侦听器的回调,这样调度器就知道允许自己派发更新
job.allowRecurse = !!cb
let scheduler: EffectScheduler
// sync 表示同步的watcher
if (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 = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
// 初始化getter函数和调度器函数scheduler后, 调用reactiveeffect
const effect = new ReactiveEffect(getter, NOOP, scheduler)
const scope = getCurrentScope()
const unwatch = () => {
effect.stop()
if (scope) {
remove(scope.effects, effect)
}
}
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
// initial run
if (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 getter
if(typeof souce === 'function') {
getter = souce
}else {
getter = ()=> traverse(souce)
}
let newVal,oldVal;
let work = ()=>{
//调用effectFn,得到新的newVal
newVal = effectFn()
//当响应式数据变化时候,会执行回调函数callBack()
callBack(newVal,oldVal)
//新值替换旧值,当作旧值。
oldVal = newVal
}
//我们对传入的对象souce进行循环遍历,把所有属性进行监听
const effectFn = effect(()=>getter(),{
lazy:true,//lazy 是懒执行effect
scheduler: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 result
let tracking: boolean | undefined
if (
effect._dirtyLevel < dirtyLevel &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
effect._dirtyLevel = dirtyLevel
}
if (
effect._shouldSchedule &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
if (__DEV__) {
// eslint-disable-next-line no-restricted-syntax
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
}
effect.trigger()
if (
(!effect._runnings || effect.allowRecurse) &&
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
) {
effect._shouldSchedule = false
if (effect.scheduler) {
queueEffectSchedulers.push(effect.scheduler)
}
}
}
}
resetScheduling()
}
export function pauseScheduling() {
pauseScheduleStack++
}
export function resetScheduling() {
pauseScheduleStack--
while (!pauseScheduleStack && queueEffectSchedulers.length) {
queueEffectSchedulers.shift()!()
}
}