, keyof T>
diff --git a/src/component/componentProps.ts b/src/component/componentProps.ts
new file mode 100644
index 00000000..0cac1bc5
--- /dev/null
+++ b/src/component/componentProps.ts
@@ -0,0 +1,100 @@
+import { Data } from './common'
+
+export type ComponentPropsOptions =
+ | ComponentObjectPropsOptions
+ | string[]
+
+export type ComponentObjectPropsOptions
= {
+ [K in keyof P]: Prop
| null
+}
+
+export type Prop = PropOptions | PropType
+
+type DefaultFactory = () => T | null | undefined
+
+export interface PropOptions {
+ type?: PropType | true | null
+ required?: boolean
+ default?: D | DefaultFactory | null | undefined | object
+ validator?(value: unknown): boolean
+}
+
+export type PropType = PropConstructor | PropConstructor[]
+
+type PropConstructor =
+ | { new (...args: any[]): T & object }
+ | { (): T }
+ | { new (...args: string[]): Function }
+
+type RequiredKeys = {
+ [K in keyof T]: T[K] extends
+ | { required: true }
+ | { default: any }
+ | BooleanConstructor
+ | { type: BooleanConstructor }
+ ? K
+ : never
+}[keyof T]
+
+type OptionalKeys = Exclude>
+
+type ExtractFunctionPropType<
+ T extends Function,
+ TArgs extends Array = any[],
+ TResult = any
+> = T extends (...args: TArgs) => TResult ? T : never
+
+type ExtractCorrectPropType = T extends Function
+ ? ExtractFunctionPropType
+ : Exclude
+
+// prettier-ignore
+type InferPropType = T extends null
+ ? any // null & true would fail to infer
+ : T extends { type: null | true }
+ ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
+ : T extends ObjectConstructor | { type: ObjectConstructor }
+ ? Record
+ : T extends BooleanConstructor | { type: BooleanConstructor }
+ ? boolean
+ : T extends DateConstructor | { type: DateConstructor}
+ ? Date
+ : T extends FunctionConstructor | { type: FunctionConstructor }
+ ? Function
+ : T extends Prop
+ ? unknown extends V
+ ? D extends null | undefined
+ ? V
+ : D
+ : ExtractCorrectPropType
+ : T
+
+export type ExtractPropTypes = {
+ // use `keyof Pick>` instead of `RequiredKeys` to support IDE features
+ [K in keyof Pick>]: InferPropType
+} & {
+ // use `keyof Pick>` instead of `OptionalKeys` to support IDE features
+ [K in keyof Pick>]?: InferPropType
+}
+
+type DefaultKeys = {
+ [K in keyof T]: T[K] extends
+ | {
+ default: any
+ }
+ | BooleanConstructor
+ | { type: BooleanConstructor }
+ ? T[K] extends {
+ type: BooleanConstructor
+ required: true
+ }
+ ? never
+ : K
+ : never
+}[keyof T]
+
+// extract props which defined with default from prop options
+export type ExtractDefaultPropTypes = O extends object
+ ? // use `keyof Pick>` instead of `DefaultKeys` to support IDE features
+ { [K in keyof Pick>]: InferPropType }
+ : {}
diff --git a/src/component/componentProxy.ts b/src/component/componentProxy.ts
new file mode 100644
index 00000000..5199e2c6
--- /dev/null
+++ b/src/component/componentProxy.ts
@@ -0,0 +1,193 @@
+import { ExtractDefaultPropTypes, ExtractPropTypes } from './componentProps'
+import {
+ nextTick,
+ ShallowUnwrapRef,
+ UnwrapNestedRefs,
+ WatchOptions,
+ WatchStopHandle,
+} from '..'
+import { Data } from './common'
+
+import Vue, {
+ VueConstructor,
+ ComponentOptions as Vue2ComponentOptions,
+} from 'vue'
+import {
+ ComputedOptions,
+ MethodOptions,
+ ExtractComputedReturns,
+} from './componentOptions'
+import {
+ ComponentInternalInstance,
+ ComponentRenderEmitFn,
+ EmitFn,
+ EmitsOptions,
+ ObjectEmitsOptions,
+ Slots,
+} from '../runtimeContext'
+
+type EmitsToProps = T extends string[]
+ ? {
+ [K in string & `on${Capitalize}`]?: (...args: any[]) => any
+ }
+ : T extends ObjectEmitsOptions
+ ? {
+ [K in string &
+ `on${Capitalize}`]?: K extends `on${infer C}`
+ ? T[Uncapitalize] extends null
+ ? (...args: any[]) => any
+ : (
+ ...args: T[Uncapitalize] extends (...args: infer P) => any
+ ? P
+ : never
+ ) => any
+ : never
+ }
+ : {}
+
+export type ComponentInstance = InstanceType
+
+// public properties exposed on the proxy, which is used as the render context
+// in templates (as `this` in the render option)
+export type ComponentRenderProxy<
+ P = {}, // props type extracted from props option
+ B = {}, // raw bindings returned from setup()
+ D = {}, // return from data()
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {},
+ PublicProps = P,
+ Defaults = {},
+ MakeDefaultsOptional extends boolean = false
+> = {
+ $data: D
+ $props: Readonly<
+ MakeDefaultsOptional extends true
+ ? Partial & Omit
+ : P & PublicProps
+ >
+ $attrs: Record
+ $emit: ComponentRenderEmitFn<
+ Emits,
+ keyof Emits,
+ ComponentRenderProxy<
+ P,
+ B,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits,
+ PublicProps,
+ Defaults,
+ MakeDefaultsOptional
+ >
+ >
+} & Readonly &
+ ShallowUnwrapRef &
+ D &
+ M &
+ ExtractComputedReturns &
+ Omit
+
+// for Vetur and TSX support
+type VueConstructorProxy<
+ PropsOptions,
+ RawBindings,
+ Data,
+ Computed extends ComputedOptions,
+ Methods extends MethodOptions,
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {},
+ Props = ExtractPropTypes &
+ ({} extends Emits ? {} : EmitsToProps)
+> = Omit & {
+ new (...args: any[]): ComponentRenderProxy<
+ Props,
+ ShallowUnwrapRef,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ Emits,
+ Props,
+ ExtractDefaultPropTypes,
+ true
+ >
+}
+
+type DefaultData = object | ((this: V) => object)
+type DefaultMethods = { [key: string]: (this: V, ...args: any[]) => any }
+type DefaultComputed = { [key: string]: any }
+
+export type VueProxy<
+ PropsOptions,
+ RawBindings,
+ Data = DefaultData,
+ Computed extends ComputedOptions = DefaultComputed,
+ Methods extends MethodOptions = DefaultMethods,
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {}
+> = Vue2ComponentOptions<
+ Vue,
+ ShallowUnwrapRef & Data,
+ Methods,
+ Computed,
+ PropsOptions,
+ ExtractPropTypes
+> &
+ VueConstructorProxy<
+ PropsOptions,
+ RawBindings,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ Emits
+ >
+
+// public properties exposed on the proxy, which is used as the render context
+// in templates (as `this` in the render option)
+export type ComponentPublicInstance<
+ P = {}, // props type extracted from props option
+ B = {}, // raw bindings returned from setup()
+ D = {}, // return from data()
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ E extends EmitsOptions = {},
+ PublicProps = P,
+ Defaults = {},
+ MakeDefaultsOptional extends boolean = false
+> = {
+ $: ComponentInternalInstance
+ $data: D
+ $props: MakeDefaultsOptional extends true
+ ? Partial & Omit
+ : P & PublicProps
+ $attrs: Data
+ $refs: Data
+ $slots: Slots
+ $root: ComponentPublicInstance | null
+ $parent: ComponentPublicInstance | null
+ $emit: EmitFn
+ $el: any
+ // $options: Options & MergedComponentOptionsOverride
+ $forceUpdate: () => void
+ $nextTick: typeof nextTick
+ $watch(
+ source: string | Function,
+ cb: Function,
+ options?: WatchOptions
+ ): WatchStopHandle
+} & P &
+ ShallowUnwrapRef &
+ UnwrapNestedRefs &
+ ExtractComputedReturns &
+ M
diff --git a/src/component/defineAsyncComponent.ts b/src/component/defineAsyncComponent.ts
new file mode 100644
index 00000000..a5c40894
--- /dev/null
+++ b/src/component/defineAsyncComponent.ts
@@ -0,0 +1,128 @@
+import { isFunction, isObject, warn } from '../utils'
+import { VueProxy } from './componentProxy'
+import { AsyncComponent } from 'vue'
+
+import {
+ ComponentOptionsWithoutProps,
+ ComponentOptionsWithArrayProps,
+ ComponentOptionsWithProps,
+} from './componentOptions'
+
+type Component = VueProxy
+
+type ComponentOrComponentOptions =
+ // Component
+ | Component
+ // ComponentOptions
+ | ComponentOptionsWithoutProps
+ | ComponentOptionsWithArrayProps
+ | ComponentOptionsWithProps
+
+export type AsyncComponentResolveResult =
+ | T
+ | { default: T } // es modules
+
+export type AsyncComponentLoader = () => Promise
+
+export interface AsyncComponentOptions {
+ loader: AsyncComponentLoader
+ loadingComponent?: ComponentOrComponentOptions
+ errorComponent?: ComponentOrComponentOptions
+ delay?: number
+ timeout?: number
+ suspensible?: boolean
+ onError?: (
+ error: Error,
+ retry: () => void,
+ fail: () => void,
+ attempts: number
+ ) => any
+}
+
+export function defineAsyncComponent(
+ source: AsyncComponentLoader | AsyncComponentOptions
+): AsyncComponent {
+ if (isFunction(source)) {
+ source = { loader: source }
+ }
+
+ const {
+ loader,
+ loadingComponent,
+ errorComponent,
+ delay = 200,
+ timeout, // undefined = never times out
+ suspensible = false, // in Vue 3 default is true
+ onError: userOnError,
+ } = source
+
+ if (__DEV__ && suspensible) {
+ warn(
+ `The suspensiblbe option for async components is not supported in Vue2. It is ignored.`
+ )
+ }
+
+ let pendingRequest: Promise | null = null
+
+ let retries = 0
+ const retry = () => {
+ retries++
+ pendingRequest = null
+ return load()
+ }
+
+ const load = (): Promise => {
+ let thisRequest: Promise
+ return (
+ pendingRequest ||
+ (thisRequest = pendingRequest =
+ loader()
+ .catch((err) => {
+ err = err instanceof Error ? err : new Error(String(err))
+ if (userOnError) {
+ return new Promise((resolve, reject) => {
+ const userRetry = () => resolve(retry())
+ const userFail = () => reject(err)
+ userOnError(err, userRetry, userFail, retries + 1)
+ })
+ } else {
+ throw err
+ }
+ })
+ .then((comp: any) => {
+ if (thisRequest !== pendingRequest && pendingRequest) {
+ return pendingRequest
+ }
+ if (__DEV__ && !comp) {
+ warn(
+ `Async component loader resolved to undefined. ` +
+ `If you are using retry(), make sure to return its return value.`
+ )
+ }
+ // interop module default
+ if (
+ comp &&
+ (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
+ ) {
+ comp = comp.default
+ }
+ if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
+ throw new Error(`Invalid async component load result: ${comp}`)
+ }
+ return comp
+ }))
+ )
+ }
+
+ return () => {
+ const component = load()
+
+ return {
+ component,
+ delay,
+ timeout,
+ error: errorComponent,
+ loading: loadingComponent,
+ }
+ }
+}
diff --git a/src/component/defineComponent.ts b/src/component/defineComponent.ts
new file mode 100644
index 00000000..a6a035f7
--- /dev/null
+++ b/src/component/defineComponent.ts
@@ -0,0 +1,118 @@
+import { ComponentPropsOptions } from './componentProps'
+import {
+ MethodOptions,
+ ComputedOptions,
+ ComponentOptionsWithoutProps,
+ ComponentOptionsWithArrayProps,
+ ComponentOptionsWithProps,
+} from './componentOptions'
+import { VueProxy } from './componentProxy'
+import { Data } from './common'
+import { HasDefined } from '../types/basic'
+import { EmitsOptions } from '../runtimeContext'
+
+/**
+ * overload 1: object format with no props
+ */
+export function defineComponent<
+ RawBindings,
+ D = Data,
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {}
+>(
+ options: ComponentOptionsWithoutProps<
+ {},
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits
+ >
+): VueProxy<{}, RawBindings, D, C, M, Mixin, Extends, Emits>
+/**
+ * overload 2: object format with array props declaration
+ * props inferred as `{ [key in PropNames]?: any }`
+ *
+ * return type is for Vetur and TSX support
+ */
+export function defineComponent<
+ PropNames extends string,
+ RawBindings = Data,
+ D = Data,
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {},
+ PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
+>(
+ options: ComponentOptionsWithArrayProps<
+ PropNames,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits
+ >
+): VueProxy<
+ Readonly<{ [key in PropNames]?: any }>,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits
+>
+
+/**
+ * overload 3: object format with object props declaration
+ *
+ * see `ExtractPropTypes` in './componentProps.ts'
+ */
+export function defineComponent<
+ Props,
+ RawBindings = Data,
+ D = Data,
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin = {},
+ Extends = {},
+ Emits extends EmitsOptions = {},
+ PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
+>(
+ options: HasDefined extends true
+ ? ComponentOptionsWithProps<
+ PropsOptions,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits,
+ Props
+ >
+ : ComponentOptionsWithProps<
+ PropsOptions,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ Emits
+ >
+): VueProxy
+
+// implementation, close to no-op
+export function defineComponent(options: any) {
+ return options as any
+}
diff --git a/src/component/directives.ts b/src/component/directives.ts
new file mode 100644
index 00000000..44d91da4
--- /dev/null
+++ b/src/component/directives.ts
@@ -0,0 +1,29 @@
+import type { VNodeDirective, VNode } from 'vue'
+
+export type DirectiveModifiers = Record
+
+export interface DirectiveBinding extends Readonly {
+ readonly modifiers: DirectiveModifiers
+ readonly value: V
+ readonly oldValue: V | null
+}
+
+export type DirectiveHook = (
+ el: T,
+ binding: DirectiveBinding,
+ vnode: VNode,
+ prevVNode: Prev
+) => void
+
+export interface ObjectDirective {
+ bind?: DirectiveHook
+ inserted?: DirectiveHook
+ update?: DirectiveHook
+ componentUpdated?: DirectiveHook
+ unbind?: DirectiveHook
+}
+export type FunctionDirective = DirectiveHook
+
+export type Directive =
+ | ObjectDirective
+ | FunctionDirective
diff --git a/src/component/index.ts b/src/component/index.ts
new file mode 100644
index 00000000..99a9f45e
--- /dev/null
+++ b/src/component/index.ts
@@ -0,0 +1,29 @@
+export { defineComponent } from './defineComponent'
+export { defineAsyncComponent } from './defineAsyncComponent'
+export {
+ SetupFunction,
+ ComputedOptions,
+ MethodOptions,
+ ComponentPropsOptions,
+} from './componentOptions'
+export {
+ ComponentInstance,
+ ComponentPublicInstance,
+ ComponentRenderProxy,
+} from './componentProxy'
+export { Data } from './common'
+export {
+ PropType,
+ PropOptions,
+ ExtractPropTypes,
+ ExtractDefaultPropTypes,
+} from './componentProps'
+
+export {
+ DirectiveModifiers,
+ DirectiveBinding,
+ DirectiveHook,
+ ObjectDirective,
+ FunctionDirective,
+ Directive,
+} from './directives'
diff --git a/src/env.d.ts b/src/env.d.ts
index 2be58b81..0433fb7a 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -1,27 +1,33 @@
-import { VueConstructor } from 'vue';
+import { VueConstructor } from 'vue'
+import { VfaState } from './utils/vmStateManager'
+import { VueWatcher } from './apis/watch'
declare global {
interface Window {
- Vue: VueConstructor;
+ Vue: VueConstructor
}
}
declare module 'vue/types/vue' {
interface Vue {
- readonly _data: Record;
+ readonly _uid: number
+ readonly _data: Record
+ _watchers: VueWatcher[]
+ _provided: Record
+ __composition_api_state__?: VfaState
}
interface VueConstructor {
- observable(x: any): T;
+ observable(x: any): T
util: {
- warn(msg: string, vm?: Vue);
+ warn(msg: string, vm?: Vue | null)
defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: Function,
shallow?: boolean
- );
- };
+ )
+ }
}
}
diff --git a/src/functions/computed.ts b/src/functions/computed.ts
deleted file mode 100644
index 6cd0c6cd..00000000
--- a/src/functions/computed.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { compoundComputed } from '../helper';
-import { Wrapper, ComputedWrapper } from '../wrappers';
-
-export function computed(getter: () => T, setter?: (x: T) => void): Wrapper {
- const computedHost = compoundComputed({
- $$state: {
- get: getter,
- set: setter,
- },
- });
-
- return new ComputedWrapper({
- read: () => computedHost.$$state,
- ...(setter && {
- write: (v: T) => {
- computedHost.$$state = v;
- },
- }),
- });
-}
diff --git a/src/functions/inject.ts b/src/functions/inject.ts
deleted file mode 100644
index 2b26e2b7..00000000
--- a/src/functions/inject.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import Vue from 'vue';
-import { getCurrentVue } from '../runtimeContext';
-import { state } from '../functions/state';
-import { isWrapper, Wrapper, ComputedWrapper } from '../wrappers';
-import { ensureCurrentVMInFn } from '../helper';
-import { hasOwn } from '../utils';
-
-const UNRESOLVED_INJECT = {};
-export interface Key extends Symbol {}
-
-function resolveInject(provideKey: Key, vm: Vue): any {
- let source = vm;
- while (source) {
- // @ts-ignore
- if (source._provided && hasOwn(source._provided, provideKey)) {
- //@ts-ignore
- return source._provided[provideKey];
- }
- source = source.$parent;
- }
-
- return UNRESOLVED_INJECT;
-}
-
-export function provide(key: Key, value: T | Wrapper) {
- const vm: any = ensureCurrentVMInFn('provide');
- if (!vm._provided) {
- vm._provided = {};
- }
- vm._provided[key as any] = value;
-}
-
-export function inject(key: Key): Wrapper | void {
- if (!key) {
- return;
- }
-
- const vm = ensureCurrentVMInFn('inject');
- const val = resolveInject(key, vm);
- if (val !== UNRESOLVED_INJECT) {
- if (isWrapper(val)) {
- return val;
- }
- const reactiveVal = state(val);
- return new ComputedWrapper({
- read: () => reactiveVal,
- write() {
- getCurrentVue().util.warn(`The injectd value can't be re-assigned`, vm);
- },
- });
- } else if (process.env.NODE_ENV !== 'production') {
- getCurrentVue().util.warn(`Injection "${String(key)}" not found`, vm);
- }
-}
diff --git a/src/functions/lifecycle.ts b/src/functions/lifecycle.ts
deleted file mode 100644
index d0e1b913..00000000
--- a/src/functions/lifecycle.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ensureCurrentVMInFn } from '../helper';
-
-const genName = (name: string) => `on${name[0].toUpperCase() + name.slice(1)}`;
-function createLifeCycle(lifeCyclehook: string) {
- return (callback: Function) => {
- const vm = ensureCurrentVMInFn(genName(lifeCyclehook));
- vm.$on(`hook:${lifeCyclehook}`, callback);
- };
-}
-
-function createLifeCycles(lifeCyclehooks: string[], name: string) {
- return (callback: Function) => {
- const vm = ensureCurrentVMInFn(name);
- lifeCyclehooks.forEach(lifeCyclehook => vm.$on(`hook:${lifeCyclehook}`, callback));
- };
-}
-
-export const onCreated = createLifeCycle('created');
-export const onBeforeMount = createLifeCycle('beforeMount');
-export const onMounted = createLifeCycle('mounted');
-export const onBeforeUpdate = createLifeCycle('beforeUpdate');
-export const onUpdated = createLifeCycle('updated');
-export const onActivated = createLifeCycle('activated');
-export const onDeactivated = createLifeCycle('deactivated');
-export const onBeforeDestroy = createLifeCycle('beforeDestroy');
-export const onDestroyed = createLifeCycle('destroyed');
-export const onErrorCaptured = createLifeCycle('errorCaptured');
-
-// only one event will be fired between destroyed and deactivated when an unmount occurs
-export const onUnmounted = createLifeCycles(['destroyed', 'deactivated'], genName('unmounted'));
diff --git a/src/functions/state.ts b/src/functions/state.ts
deleted file mode 100644
index 89786a23..00000000
--- a/src/functions/state.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Wrapper, ValueWrapper } from '../wrappers';
-import { observable } from '../reactivity';
-
-export function state(value: T): T {
- return observable(value);
-}
-
-export function value(value: T): Wrapper {
- return new ValueWrapper(state({ $$state: value }));
-}
diff --git a/src/functions/watch.ts b/src/functions/watch.ts
deleted file mode 100644
index 7ff766a6..00000000
--- a/src/functions/watch.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import Vue, { VueConstructor } from 'vue';
-import { Wrapper } from '../wrappers';
-import { isArray, assert } from '../utils';
-import { isWrapper } from '../wrappers';
-import { getCurrentVM, getCurrentVue } from '../runtimeContext';
-import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey } from '../symbols';
-
-const initValue = {};
-type InitValue = typeof initValue;
-type watcherCallBack = (newVal: T, oldVal: T) => void;
-type watchedValue = Wrapper | (() => T);
-type FlushMode = 'pre' | 'post' | 'sync';
-interface WatcherOption {
- lazy: boolean;
- deep: boolean;
- flush: FlushMode;
-}
-interface WatcherContext {
- getter: () => T;
- value: T | InitValue;
- oldValue: T | InitValue;
- watcherStopHandle: Function;
-}
-
-let fallbackVM: Vue;
-
-function hasWatchEnv(vm: any) {
- return vm[WatcherPreFlushQueueKey] !== undefined;
-}
-
-function installWatchEnv(vm: any) {
- vm[WatcherPreFlushQueueKey] = [];
- vm[WatcherPostFlushQueueKey] = [];
- vm.$on('hook:beforeUpdate', createFlusher(WatcherPreFlushQueueKey));
- vm.$on('hook:updated', createFlusher(WatcherPostFlushQueueKey));
-}
-
-function createFlusher(key: any) {
- return function(this: any) {
- flushQueue(this, key);
- };
-}
-
-function flushQueue(vm: any, key: any) {
- const queue = vm[key];
- for (let index = 0; index < queue.length; index++) {
- queue[index]();
- }
- queue.length = 0;
-}
-
-function flushWatcherCallback(vm: any, fn: Function, mode: FlushMode) {
- // flush all when beforeUpdate and updated are not fired
- function fallbackFlush() {
- vm.$nextTick(() => {
- if (vm[WatcherPreFlushQueueKey].length) {
- flushQueue(vm, WatcherPreFlushQueueKey);
- }
- if (vm[WatcherPostFlushQueueKey].length) {
- flushQueue(vm, WatcherPostFlushQueueKey);
- }
- });
- }
-
- switch (mode) {
- case 'pre':
- fallbackFlush();
- vm[WatcherPreFlushQueueKey].push(fn);
- break;
- case 'post':
- fallbackFlush();
- vm[WatcherPostFlushQueueKey].push(fn);
- break;
- case 'sync':
- fn();
- break;
- default:
- assert(false, `flush must be one of ["post", "pre", "sync"], but got ${mode}`);
- break;
- }
-}
-
-function createSingleSourceWatcher(
- vm: InstanceType,
- source: watchedValue,
- cb: watcherCallBack,
- options: WatcherOption
-): () => void {
- let getter: () => T;
- if (isWrapper(source)) {
- getter = () => source.value;
- } else {
- getter = source as () => T;
- }
-
- let callbackRef = (n: T, o: T) => {
- callbackRef = flush;
-
- if (!options.lazy) {
- cb(n, o);
- } else {
- flush(n, o);
- }
- };
-
- const flush = (n: T, o: T) => {
- flushWatcherCallback(
- vm,
- () => {
- cb(n, o);
- },
- options.flush
- );
- };
-
- return vm.$watch(
- getter,
- (n: T, o: T) => {
- callbackRef(n, o);
- },
- {
- immediate: !options.lazy,
- deep: options.deep,
- // @ts-ignore
- sync: options.flush === 'sync',
- }
- );
-}
-
-function createMuiltSourceWatcher(
- vm: InstanceType,
- sources: Array>,
- cb: watcherCallBack,
- options: WatcherOption
-): () => void {
- let execCallbackAfterNumRun: false | number = options.lazy ? false : sources.length;
- let pendingCallback = false;
- const watcherContext: Array> = [];
-
- function execCallback() {
- cb.apply(
- vm,
- watcherContext.reduce<[T[], T[]]>(
- (acc, ctx) => {
- acc[0].push((ctx.value === initValue ? ctx.getter() : ctx.value) as T);
- acc[1].push((ctx.oldValue === initValue ? undefined : ctx.oldValue) as T);
- return acc;
- },
- [[], []]
- )
- );
- }
- function stop() {
- watcherContext.forEach(ctx => ctx.watcherStopHandle());
- }
-
- let callbackRef = () => {
- if (execCallbackAfterNumRun !== false) {
- if (--execCallbackAfterNumRun === 0) {
- execCallbackAfterNumRun = false;
- callbackRef = flush;
- execCallback();
- }
- } else {
- callbackRef = flush;
- flush();
- }
- };
-
- const flush = () => {
- if (!pendingCallback) {
- pendingCallback = true;
- vm.$nextTick(() => {
- flushWatcherCallback(
- vm,
- () => {
- pendingCallback = false;
- execCallback();
- },
- options.flush
- );
- });
- }
- };
-
- sources.forEach(source => {
- let getter: () => T;
- if (isWrapper(source)) {
- getter = () => source.value;
- } else {
- getter = source as () => T;
- }
- const watcherCtx = {
- getter,
- value: initValue,
- oldValue: initValue,
- } as WatcherContext;
- // must push watcherCtx before create watcherStopHandle
- watcherContext.push(watcherCtx);
-
- watcherCtx.watcherStopHandle = vm.$watch(
- getter,
- (n: T, o: T) => {
- watcherCtx.value = n;
- watcherCtx.oldValue = o;
-
- callbackRef();
- },
- {
- immediate: !options.lazy,
- deep: options.deep,
- // @ts-ignore
- // always set to true, so we can fully control the schedule
- sync: true,
- }
- );
- });
-
- return stop;
-}
-
-export function watch(
- source: watchedValue,
- cb: watcherCallBack,
- options?: Partial
-): () => void;
-export function watch(
- source: Array>,
- cb: watcherCallBack