let value: any = undefined
const runner = effect(() => getter.call(context, context), {
lazy: true,
+ // mark effect as computed so that it gets priority during trigger
+ computed: true,
scheduler: () => {
dirty = true
}
})
- // mark effect as computed so that it gets priority during trigger
- runner.computed = true
const computedValue = {
// expose effect so computed can be stopped
effect: runner,
export interface ReactiveEffectOptions {
lazy?: boolean
+ computed?: boolean
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
effect.scheduler = options.scheduler
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
+ effect.computed = options.computed
effect.deps = []
return effect
}
DebuggerEvent
} from './effect'
-import { UnwrapBindings } from './value'
+import { UnwrapValues } from './value'
export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent }
export { OperationTypes } from './operations'
export { computed, ComputedValue } from './computed'
export { lock, unlock } from './lock'
-export { value, isValue, Value, UnwrapBindings } from './value'
+export { value, isValue, Value, UnwrapValues } from './value'
const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet])
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
)
}
-type ObservableFactory = <T>(target?: T) => UnwrapBindings<T>
+type ObservableFactory = <T>(target?: T) => UnwrapValues<T>
export const observable = ((target: any = {}): any => {
// if trying to observe an immutable proxy, return the immutable version.
value: T
}
+const convert = (val: any): any => (isObject(val) ? observable(val) : val)
+
+export function value<T>(raw: T): Value<T> {
+ raw = convert(raw)
+ const v = {
+ get value() {
+ track(v, OperationTypes.GET, '')
+ return raw
+ },
+ set value(newVal) {
+ raw = convert(newVal)
+ trigger(v, OperationTypes.SET, '')
+ }
+ }
+ knownValues.add(v)
+ return v
+}
+
+export function isValue(v: any): v is Value<any> {
+ return knownValues.has(v)
+}
+
type UnwrapValue<T, U = T> = T extends Value<infer V> ? V : T extends {} ? U : T
// A utility type that recursively unwraps value bindings nested inside an
// observable object. Unfortunately TS cannot do recursive types, but this
// should be enough for practical use cases...
-export type UnwrapBindings<T> = {
+export type UnwrapValues<T> = {
[key in keyof T]: UnwrapValue<
T[key],
{
}
>
}
-
-const convert = (val: any): any => (isObject(val) ? observable(val) : val)
-
-export function value<T>(raw: T): Value<T> {
- raw = convert(raw)
- const v = {
- get value() {
- track(v, OperationTypes.GET, '')
- return raw
- },
- set value(newVal) {
- raw = convert(newVal)
- trigger(v, OperationTypes.SET, '')
- }
- }
- knownValues.add(v)
- return v
-}
-
-export function isValue(v: any): boolean {
- return knownValues.has(v)
-}
import { VNode, normalizeVNode, VNodeChild } from './vnode'
-import { ReactiveEffect, UnwrapBindings, observable } from '@vue/observer'
+import { ReactiveEffect, UnwrapValues, observable } from '@vue/observer'
import { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, PropValidator } from './componentProps'
RawProps = ComponentPropsOptions,
RawBindings = Data | void,
Props = ExtractPropTypes<RawProps>,
- Bindings = UnwrapBindings<RawBindings>
+ Bindings = UnwrapValues<RawBindings>
> {
props?: RawProps
setup?: (props: Props) => RawBindings
next: VNode | null
subTree: VNode
update: ReactiveEffect
+ effects: ReactiveEffect[] | null
// the rest are only for stateful components
proxy: ComponentPublicProperties | null
state: S
RawProps,
RawBindings,
Props = ExtractPropTypes<RawProps>,
- Bindings = UnwrapBindings<RawBindings>
+ Bindings = UnwrapValues<RawBindings>
>(
options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
): {
rtg: null,
rtc: null,
ec: null,
+ effects: null,
// public properties
state: EMPTY_OBJ,
target: ComponentInstance | null | void = currentInstance
) {
if (target) {
- const existing = target[name]
// TODO inject a error-handling wrapped version of the hook
- if (existing !== null) {
- existing.push(hook)
- } else {
- target[name] = [hook]
- }
+ ;(target[name] || (target[name] = [])).push(hook)
} else {
// TODO warn
}
function unmount(vnode: VNode, doRemove?: boolean) {
const instance = vnode.component
if (instance != null) {
- // beforeUnmount hook
- if (instance.bum !== null) {
- invokeHooks(instance.bum)
- }
- // TODO teardown component
- stop(instance.update)
- unmount(instance.subTree, doRemove)
- // unmounted hook
- if (instance.um !== null) {
- queuePostFlushCb(instance.um)
- }
+ unmountComponent(instance, doRemove)
return
}
const shouldRemoveChildren = vnode.type === Fragment && doRemove
}
}
+ function unmountComponent(
+ { bum, effects, update, subTree, um }: ComponentInstance,
+ doRemove?: boolean
+ ) {
+ // beforeUnmount hook
+ if (bum !== null) {
+ invokeHooks(bum)
+ }
+ if (effects !== null) {
+ for (let i = 0; i < effects.length; i++) {
+ stop(effects[i])
+ }
+ }
+ stop(update)
+ unmount(subTree, doRemove)
+ // unmounted hook
+ if (um !== null) {
+ queuePostFlushCb(um)
+ }
+ }
+
function unmountChildren(
children: VNode[],
doRemove?: boolean,
export { createRenderer, RendererOptions } from './createRenderer'
export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-export * from '@vue/observer'
+export * from './reactivity'
--- /dev/null
+export {
+ value,
+ isValue,
+ observable,
+ immutable,
+ isObservable,
+ isImmutable,
+ unwrap,
+ markImmutable,
+ markNonReactive,
+ effect,
+ // types
+ ReactiveEffect,
+ ReactiveEffectOptions,
+ DebuggerEvent,
+ OperationTypes,
+ Value,
+ ComputedValue,
+ UnwrapValues
+} from '@vue/observer'
+
+import {
+ effect,
+ stop,
+ computed as _computed,
+ isValue,
+ Value,
+ ComputedValue,
+ ReactiveEffect,
+ ReactiveEffectOptions
+} from '@vue/observer'
+import { currentInstance } from './component'
+import { queueJob, queuePostFlushCb } from './scheduler'
+import { EMPTY_OBJ, isObject, isArray } from '@vue/shared'
+
+function recordEffect(effect: ReactiveEffect) {
+ if (currentInstance) {
+ ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
+ }
+}
+
+// a wrapped version of raw computed to tear it down at component unmount
+export function computed<T, C = null>(
+ getter: (this: C, ctx: C) => T,
+ context?: C
+): ComputedValue<T> {
+ const c = _computed(getter, context)
+ recordEffect(c.effect)
+ return c
+}
+
+export interface WatchOptions {
+ lazy?: boolean
+ flush?: 'pre' | 'post' | 'sync'
+ deep?: boolean
+ onTrack?: ReactiveEffectOptions['onTrack']
+ onTrigger?: ReactiveEffectOptions['onTrigger']
+}
+
+const invoke = (fn: Function) => fn()
+
+export function watch<T>(
+ source: Value<T> | (() => T),
+ cb?: <V extends T>(newValue: V, oldValue: V) => (() => void) | void,
+ options: WatchOptions = EMPTY_OBJ
+): () => void {
+ const scheduler =
+ options.flush === 'sync'
+ ? invoke
+ : options.flush === 'pre'
+ ? queueJob
+ : queuePostFlushCb
+
+ const traverseIfDeep = (getter: Function) =>
+ options.deep ? () => traverse(getter()) : getter
+ const getter = isValue(source)
+ ? traverseIfDeep(() => source.value)
+ : traverseIfDeep(source)
+
+ let oldValue: any
+ const applyCb = cb
+ ? () => {
+ const newValue = runner()
+ if (options.deep || newValue !== oldValue) {
+ try {
+ cb(newValue, oldValue)
+ } catch (e) {
+ // TODO handle error
+ // handleError(e, instance, ErrorTypes.WATCH_CALLBACK)
+ }
+ oldValue = newValue
+ }
+ }
+ : void 0
+
+ const runner = effect(getter, {
+ lazy: true,
+ // so it runs before component update effects in pre flush mode
+ computed: true,
+ onTrack: options.onTrack,
+ onTrigger: options.onTrigger,
+ scheduler: applyCb ? () => scheduler(applyCb) : void 0
+ })
+
+ if (!options.lazy) {
+ applyCb && scheduler(applyCb)
+ } else {
+ oldValue = runner()
+ }
+
+ recordEffect(runner)
+ return () => {
+ stop(runner)
+ }
+}
+
+function traverse(value: any, seen: Set<any> = new Set()) {
+ if (!isObject(value) || seen.has(value)) {
+ return
+ }
+ seen.add(value)
+ if (isArray(value)) {
+ for (let i = 0; i < value.length; i++) {
+ traverse(value[i], seen)
+ }
+ } else if (value instanceof Map || value instanceof Set) {
+ ;(value as any).forEach((v: any) => {
+ traverse(v, seen)
+ })
+ } else {
+ for (const key in value) {
+ traverse(value[key], seen)
+ }
+ }
+ return value
+}
type,
props,
key: props && props.key,
- children,
+ children: typeof children === 'number' ? children + '' : children,
component: null,
el: null,
anchor: null,