parent
9cf1354502
commit
cff02e6911
@ -1,3 +1,5 @@ |
|||||||
export enum ReactiveFlags { |
export enum ReactiveFlags { |
||||||
IS_SIGNAL = '__v_isSignal', |
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