parent
9cf1354502
commit
cff02e6911
@ -1,3 +1,5 @@ |
||||
export enum ReactiveFlags { |
||||
IS_SIGNAL = '__v_isSignal', |
||||
SKIP = "__v_skip", |
||||
IS_SHALLOW = "__v_isShallow", |
||||
} |
||||
|
@ -0,0 +1,80 @@ |
||||
import { ReactiveFlags } from "./contents" |
||||
import { isSignal } from "./core" |
||||
|
||||
export const objectToString: typeof Object.prototype.toString = |
||||
Object.prototype.toString |
||||
export const toTypeString = (value: unknown): string => |
||||
objectToString.call(value) |
||||
const hasOwnProperty = Object.prototype.hasOwnProperty |
||||
export const hasOwn = ( |
||||
val: object, |
||||
key: string | symbol, |
||||
): key is keyof typeof val => hasOwnProperty.call(val, key) |
||||
|
||||
export const isArray: typeof Array.isArray = Array.isArray |
||||
export const isMap = (val: unknown): val is Map<any, any> => |
||||
toTypeString(val) === '[object Map]' |
||||
export const isSet = (val: unknown): val is Set<any> => |
||||
toTypeString(val) === '[object Set]' |
||||
|
||||
export const isDate = (val: unknown): val is Date => |
||||
toTypeString(val) === '[object Date]' |
||||
export const isRegExp = (val: unknown): val is RegExp => |
||||
toTypeString(val) === '[object RegExp]' |
||||
export const isFunction = (val: unknown): val is Function => |
||||
typeof val === 'function' |
||||
export const isString = (val: unknown): val is string => typeof val === 'string' |
||||
export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol' |
||||
export const isObject = (val: unknown): val is Record<any, any> => |
||||
val !== null && typeof val === 'object' |
||||
|
||||
export const isPromise = <T = any>(val: unknown): val is Promise<T> => { |
||||
return ( |
||||
(isObject(val) || isFunction(val)) && |
||||
isFunction((val as any).then) && |
||||
isFunction((val as any).catch) |
||||
) |
||||
} |
||||
export const isPlainObject = (val: unknown): val is object => |
||||
toTypeString(val) === '[object Object]' |
||||
|
||||
export const hasChanged = (value: any, oldValue: any): boolean => |
||||
!Object.is(value, oldValue) |
||||
|
||||
export function traverse( |
||||
value: unknown, |
||||
depth: number = Infinity, |
||||
seen?: Set<unknown>, |
||||
): 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 (isSignal(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 |
||||
} |
@ -0,0 +1,152 @@ |
||||
import { Computed, Effect, isSignal, Signal, } from './core'; |
||||
import { hasChanged, isArray, traverse } from './utils'; |
||||
import { isDeepSignal, isShallow } from "./deepSignal" |
||||
|
||||
export type OnCleanup = (cleanupFn: () => void) => void |
||||
export type WatchEffect = (onCleanup: OnCleanup) => void |
||||
|
||||
export type WatchSource<T = any> = Signal<T> | Computed<T> | (() => T) |
||||
|
||||
export interface WatchOptions<Immediate = boolean> { |
||||
immediate?: Immediate |
||||
deep?: boolean | number |
||||
once?: boolean |
||||
} |
||||
|
||||
export type WatchCallback<V = any, OV = any> = ( |
||||
value: V, |
||||
oldValue: OV, |
||||
onCleanup: OnCleanup, |
||||
) => any |
||||
|
||||
const INITIAL_WATCHER_VALUE = {} |
||||
let activeWatcher!: Effect |
||||
|
||||
export const remove = <T>(arr: T[], el: T): void => { |
||||
const i = arr.indexOf(el) |
||||
if (i > -1) { |
||||
arr.splice(i, 1) |
||||
} |
||||
} |
||||
|
||||
export function watch( |
||||
source: WatchSource | WatchSource[] | WatchEffect | object, |
||||
cb?: WatchCallback, |
||||
options: WatchOptions = {} |
||||
) { |
||||
const { once, immediate, deep } = options |
||||
|
||||
let effect!: Effect |
||||
let getter!: () => any |
||||
// let boundCleanup: typeof onWatcherCleanup
|
||||
let forceTrigger = false |
||||
let isMultiSource = false |
||||
|
||||
const signalGetter = (source: object) => { |
||||
// traverse will happen in wrapped getter below
|
||||
if (deep) return source |
||||
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
|
||||
if (isShallow(source) || deep === false || deep === 0) |
||||
return traverse(source, 1) |
||||
// for `deep: undefined` on a reactive object, deeply traverse all properties
|
||||
return traverse(source) |
||||
} |
||||
|
||||
const watchHandle = () => { |
||||
effect.stop() |
||||
return effect |
||||
} |
||||
|
||||
if (once && cb) { |
||||
const _cb = cb |
||||
cb = (...args) => { |
||||
_cb(...args) |
||||
watchHandle() |
||||
} |
||||
} |
||||
|
||||
if (isSignal(source)) { |
||||
getter = () => source.value |
||||
forceTrigger = isShallow(source) |
||||
} else if (isDeepSignal(source)) { |
||||
getter = () => { |
||||
return signalGetter(source) |
||||
} |
||||
forceTrigger = true |
||||
} else if (isArray(source)) { |
||||
isMultiSource = true |
||||
forceTrigger = source.some(s => isDeepSignal(s) || isShallow(s)) |
||||
getter = () => |
||||
source.map(s => { |
||||
if (isSignal(s)) { |
||||
return s.value |
||||
} else if (isDeepSignal(s)) { |
||||
return signalGetter(s) |
||||
} |
||||
}) |
||||
} |
||||
if (cb && deep) { |
||||
const baseGetter = getter |
||||
const depth = deep === true ? Infinity : deep |
||||
getter = () => traverse(baseGetter(), depth) |
||||
} |
||||
|
||||
let oldValue: any = isMultiSource |
||||
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) |
||||
: INITIAL_WATCHER_VALUE |
||||
|
||||
const job = (immediateFirstRun?: boolean) => { |
||||
if (!effect.active || (!immediateFirstRun && !effect.dirty)) { |
||||
return |
||||
} |
||||
if (cb) { |
||||
// watch(source, cb)
|
||||
const newValue = effect.run() |
||||
|
||||
if ( |
||||
deep || |
||||
forceTrigger || |
||||
(isMultiSource |
||||
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i])) |
||||
: hasChanged(newValue, oldValue)) |
||||
) { |
||||
const currentWatcher = activeWatcher |
||||
activeWatcher = effect |
||||
try { |
||||
const args = [ |
||||
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, |
||||
effect.stop, |
||||
] |
||||
// @ts-ignore
|
||||
cb!(...args) |
||||
oldValue = newValue |
||||
} finally { |
||||
activeWatcher = currentWatcher |
||||
} |
||||
} |
||||
} else { |
||||
// watchEffect
|
||||
effect.run() |
||||
} |
||||
} |
||||
|
||||
effect = new Effect(getter) |
||||
effect.scheduler = job |
||||
|
||||
if (cb) { |
||||
if (immediate) { |
||||
job(true) |
||||
} else { |
||||
oldValue = effect.run() |
||||
} |
||||
} else { |
||||
effect.run() |
||||
} |
||||
return watchHandle |
||||
} |
Loading…
Reference in new issue