import { ComponentInternalInstance, Data, SetupContext, ComponentInternalOptions, Component, ConcreteComponent, InternalRenderFunction, LifecycleHooks } from './component' import { isFunction, extend, isString, isObject, isArray, EMPTY_OBJ, NOOP, hasOwn, isPromise } from '@vue/shared' import { computed } from './apiComputed' import { watch, WatchOptions, WatchCallback, createPathGetter } from './apiWatch' import { provide, inject } from './apiInject' import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onErrorCaptured, onRenderTracked, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onRenderTriggered, DebuggerHook, ErrorCapturedHook, onServerPrefetch } from './apiLifecycle' import { reactive, ComputedGetter, WritableComputedOptions, toRaw, proxyRefs, toRef } from '@vue/reactivity' import { ComponentObjectPropsOptions, ExtractPropTypes, ExtractDefaultPropTypes } from './componentProps' import { EmitsOptions } from './componentEmits' import { Directive } from './directives' import { CreateComponentPublicInstance, ComponentPublicInstance } from './componentPublicInstance' import { warn } from './warning' import { VNodeChild } from './vnode' import { callWithAsyncErrorHandling } from './errorHandling' import { UnionToIntersection } from './helpers/typeUtils' import { deepMergeData } from './compat/data' import { DeprecationTypes } from './compat/compatConfig' import { CompatConfig, isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig' import { AssetTypes, COMPONENTS, DIRECTIVES, FILTERS } from './helpers/resolveAssets' /** * Interface for declaring custom options. * * @example * ```ts * declare module '@vue/runtime-core' { * interface ComponentCustomOptions { * beforeRouteUpdate?( * to: Route, * from: Route, * next: () => void * ): void * } * } * ``` */ export interface ComponentCustomOptions {} export type RenderFunction = () => VNodeChild type ExtractOptionProp = T extends ComponentOptionsBase< infer P, any, any, any, any, any, any, any > ? unknown extends P ? {} : P : {} export interface ComponentOptionsBase< Props, RawBindings, D, C extends ComputedOptions, M extends MethodOptions, Mixin extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin, E extends EmitsOptions, EE extends string = string, Defaults = {} > extends LegacyOptions, ComponentInternalOptions, ComponentCustomOptions { setup?: ( this: void, props: Props & UnionToIntersection> & UnionToIntersection>, ctx: SetupContext ) => Promise | RawBindings | RenderFunction | void name?: string template?: string | object // can be a direct DOM node // Note: we are intentionally using the signature-less `Function` type here // since any type with signature will cause the whole inference to fail when // the return expression contains reference to `this`. // Luckily `render()` doesn't need any arguments nor does it care about return // type. render?: Function components?: Record directives?: Record inheritAttrs?: boolean emits?: (E | EE[]) & ThisType // TODO infer public instance type based on exposed keys expose?: string[] serverPrefetch?(): Promise // Runtime compiler only ----------------------------------------------------- compilerOptions?: RuntimeCompilerOptions // Internal ------------------------------------------------------------------ /** * SSR only. This is produced by compiler-ssr and attached in compiler-sfc * not user facing, so the typing is lax and for test only. * @internal */ ssrRender?: ( ctx: any, push: (item: any) => void, parentInstance: ComponentInternalInstance, attrs: Data | undefined, // for compiler-optimized bindings $props: ComponentInternalInstance['props'], $setup: ComponentInternalInstance['setupState'], $data: ComponentInternalInstance['data'], $options: ComponentInternalInstance['ctx'] ) => void /** * Only generated by compiler-sfc to mark a ssr render function inlined and * returned from setup() * @internal */ __ssrInlineRender?: boolean /** * marker for AsyncComponentWrapper * @internal */ __asyncLoader?: () => Promise /** * cache for merged $options * @internal */ __merged?: ComponentOptions // Type differentiators ------------------------------------------------------ // Note these are internal but need to be exposed in d.ts for type inference // to work! // type-only differentiator to separate OptionWithoutProps from a constructor // type returned by defineComponent() or FunctionalComponent call?: (this: unknown, ...args: unknown[]) => never // type-only differentiators for built-in Vnode types __isFragment?: never __isTeleport?: never __isSuspense?: never __defaults?: Defaults } /** * Subset of compiler options that makes sense for the runtime. */ export interface RuntimeCompilerOptions { isCustomElement?: (tag: string) => boolean whitespace?: 'preserve' | 'condense' comments?: boolean delimiters?: [string, string] } export type ComponentOptionsWithoutProps< Props = {}, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string > = ComponentOptionsBase< Props, RawBindings, D, C, M, Mixin, Extends, E, EE, {} > & { props?: undefined } & ThisType< CreateComponentPublicInstance<{}, RawBindings, D, C, M, Mixin, Extends, E> > export type ComponentOptionsWithArrayProps< PropNames extends string = string, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, Props = Readonly<{ [key in PropNames]?: any }> > = ComponentOptionsBase< Props, RawBindings, D, C, M, Mixin, Extends, E, EE, {} > & { props: PropNames[] } & ThisType< CreateComponentPublicInstance< Props, RawBindings, D, C, M, Mixin, Extends, E > > export type ComponentOptionsWithObjectProps< PropsOptions = ComponentObjectPropsOptions, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, Props = Readonly>, Defaults = ExtractDefaultPropTypes > = ComponentOptionsBase< Props, RawBindings, D, C, M, Mixin, Extends, E, EE, Defaults > & { props: PropsOptions & ThisType } & ThisType< CreateComponentPublicInstance< Props, RawBindings, D, C, M, Mixin, Extends, E, Props, Defaults, false > > export type ComponentOptions< Props = {}, RawBindings = any, D = any, C extends ComputedOptions = any, M extends MethodOptions = any, Mixin extends ComponentOptionsMixin = any, Extends extends ComponentOptionsMixin = any, E extends EmitsOptions = any > = ComponentOptionsBase & ThisType< CreateComponentPublicInstance< {}, RawBindings, D, C, M, Mixin, Extends, E, Readonly > > export type ComponentOptionsMixin = ComponentOptionsBase< any, any, any, any, any, any, any, any, any, any > export type ComputedOptions = Record< string, ComputedGetter | WritableComputedOptions > export interface MethodOptions { [key: string]: Function } export type ExtractComputedReturns = { [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn } ? TReturn : T[key] extends (...args: any[]) => infer TReturn ? TReturn : never } export type ObjectWatchOptionItem = { handler: WatchCallback | string } & WatchOptions type WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[] type ComponentWatchOptions = Record type ComponentInjectOptions = | string[] | Record< string | symbol, string | symbol | { from?: string | symbol; default?: unknown } > interface LegacyOptions< Props, D, C extends ComputedOptions, M extends MethodOptions, Mixin extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin > { compatConfig?: CompatConfig // allow any custom options [key: string]: any // state // Limitation: we cannot expose RawBindings on the `this` context for data // since that leads to some sort of circular inference and breaks ThisType // for the entire component. data?: ( this: CreateComponentPublicInstance< Props, {}, {}, {}, MethodOptions, Mixin, Extends >, vm: CreateComponentPublicInstance< Props, {}, {}, {}, MethodOptions, Mixin, Extends > ) => D computed?: C methods?: M watch?: ComponentWatchOptions provide?: Data | Function inject?: ComponentInjectOptions // assets filters?: Record // composition mixins?: Mixin[] extends?: Extends // lifecycle beforeCreate?(): void created?(): void beforeMount?(): void mounted?(): void beforeUpdate?(): void updated?(): void activated?(): void deactivated?(): void /** @deprecated use `beforeUnmount` instead */ beforeDestroy?(): void beforeUnmount?(): void /** @deprecated use `unmounted` instead */ destroyed?(): void unmounted?(): void renderTracked?: DebuggerHook renderTriggered?: DebuggerHook errorCaptured?: ErrorCapturedHook /** * runtime compile only * @deprecated use `compilerOptions.delimiters` instead. */ delimiters?: [string, string] /** * #3468 * * type-only, used to assist Mixin's type inference, * typescript will try to simplify the inferred `Mixin` type, * with the `__differenciator`, typescript won't be able to combine different mixins, * because the `__differenciator` will be different */ __differentiator?: keyof D | keyof C | keyof M } export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults' export type OptionTypesType< P = {}, B = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Defaults = {} > = { P: P B: B D: D C: C M: M Defaults: Defaults } const enum OptionTypes { PROPS = 'Props', DATA = 'Data', COMPUTED = 'Computed', METHODS = 'Methods', INJECT = 'Inject' } function createDuplicateChecker() { const cache = Object.create(null) return (type: OptionTypes, key: string) => { if (cache[key]) { warn(`${type} property "${key}" is already defined in ${cache[key]}.`) } else { cache[key] = type } } } type DataFn = (vm: ComponentPublicInstance) => any export let shouldCacheAccess = true export function applyOptions( instance: ComponentInternalInstance, options: ComponentOptions, deferredData: DataFn[] = [], deferredWatch: ComponentWatchOptions[] = [], deferredProvide: (Data | Function)[] = [], asMixin: boolean = false ) { if (__COMPAT__ && isFunction(options)) { options = options.options } const { // composition mixins, extends: extendsOptions, // state data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, // lifecycle beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, serverPrefetch, // public API expose } = options const publicThis = instance.proxy! const ctx = instance.ctx const globalMixins = instance.appContext.mixins if (asMixin && render && instance.render === NOOP) { instance.render = render as InternalRenderFunction } // applyOptions is called non-as-mixin once per instance if (!asMixin) { shouldCacheAccess = false callSyncHook( 'beforeCreate', LifecycleHooks.BEFORE_CREATE, options, instance, globalMixins ) shouldCacheAccess = true // global mixins are applied first applyMixins( instance, globalMixins, deferredData, deferredWatch, deferredProvide ) } // extending a base component... if (extendsOptions) { applyOptions( instance, extendsOptions, deferredData, deferredWatch, deferredProvide, true ) } // local mixins if (mixins) { applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide) } const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null if (__DEV__) { const [propsOptions] = instance.propsOptions if (propsOptions) { for (const key in propsOptions) { checkDuplicateProperties!(OptionTypes.PROPS, key) } } } // options initialization order (to be consistent with Vue 2): // - props (already done outside of this function) // - inject // - methods // - data (deferred since it relies on `this` access) // - computed // - watch (deferred since it relies on `this` access) if (injectOptions) { resolveInjections(injectOptions, ctx, checkDuplicateProperties) } if (methods) { for (const key in methods) { const methodHandler = (methods as MethodOptions)[key] if (isFunction(methodHandler)) { // In dev mode, we use the `createRenderContext` function to define methods to the proxy target, // and those are read-only but reconfigurable, so it needs to be redefined here if (__DEV__) { Object.defineProperty(ctx, key, { value: methodHandler.bind(publicThis), configurable: true, enumerable: true, writable: true }) } else { ctx[key] = methodHandler.bind(publicThis) } if (__DEV__) { checkDuplicateProperties!(OptionTypes.METHODS, key) } } else if (__DEV__) { warn( `Method "${key}" has type "${typeof methodHandler}" in the component definition. ` + `Did you reference the function correctly?` ) } } } if (!asMixin) { if (deferredData.length) { deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis)) } if (dataOptions) { // @ts-ignore dataOptions is not fully type safe resolveData(instance, dataOptions, publicThis) } if (__DEV__) { const rawData = toRaw(instance.data) for (const key in rawData) { checkDuplicateProperties!(OptionTypes.DATA, key) // expose data on ctx during dev if (key[0] !== '$' && key[0] !== '_') { Object.defineProperty(ctx, key, { configurable: true, enumerable: true, get: () => rawData[key], set: NOOP }) } } } } else if (dataOptions) { deferredData.push(dataOptions as DataFn) } if (computedOptions) { for (const key in computedOptions) { const opt = (computedOptions as ComputedOptions)[key] const get = isFunction(opt) ? opt.bind(publicThis, publicThis) : isFunction(opt.get) ? opt.get.bind(publicThis, publicThis) : NOOP if (__DEV__ && get === NOOP) { warn(`Computed property "${key}" has no getter.`) } const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : __DEV__ ? () => { warn( `Write operation failed: computed property "${key}" is readonly.` ) } : NOOP const c = computed({ get, set }) Object.defineProperty(ctx, key, { enumerable: true, configurable: true, get: () => c.value, set: v => (c.value = v) }) if (__DEV__) { checkDuplicateProperties!(OptionTypes.COMPUTED, key) } } } if (watchOptions) { deferredWatch.push(watchOptions) } if (!asMixin && deferredWatch.length) { deferredWatch.forEach(watchOptions => { for (const key in watchOptions) { createWatcher(watchOptions[key], ctx, publicThis, key) } }) } if (provideOptions) { deferredProvide.push(provideOptions) } if (!asMixin && deferredProvide.length) { deferredProvide.forEach(provideOptions => { const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions Reflect.ownKeys(provides).forEach(key => { provide(key, provides[key]) }) }) } // asset options. // To reduce memory usage, only components with mixins or extends will have // resolved asset registry attached to instance. if (asMixin) { resolveInstanceAssets(instance, options, COMPONENTS) resolveInstanceAssets(instance, options, DIRECTIVES) if (__COMPAT__ && isCompatEnabled(DeprecationTypes.FILTERS, instance)) { resolveInstanceAssets(instance, options, FILTERS) } } // lifecycle options if (!asMixin) { callSyncHook( 'created', LifecycleHooks.CREATED, options, instance, globalMixins ) } function registerLifecycleHook( register: Function, hook?: Function | Function[] ) { // Array lifecycle hooks are only present in the compat build if (__COMPAT__ && isArray(hook)) { hook.forEach(_hook => register(_hook.bind(publicThis))) } else if (hook) { register((hook as Function).bind(publicThis)) } } registerLifecycleHook(onBeforeMount, beforeMount) registerLifecycleHook(onMounted, mounted) registerLifecycleHook(onBeforeUpdate, beforeUpdate) registerLifecycleHook(onUpdated, updated) registerLifecycleHook(onActivated, activated) registerLifecycleHook(onDeactivated, deactivated) registerLifecycleHook(onErrorCaptured, errorCaptured) registerLifecycleHook(onRenderTracked, renderTracked) registerLifecycleHook(onRenderTriggered, renderTriggered) registerLifecycleHook(onBeforeUnmount, beforeUnmount) registerLifecycleHook(onUnmounted, unmounted) registerLifecycleHook(onServerPrefetch, serverPrefetch) if (__COMPAT__) { if ( beforeDestroy && softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance) ) { registerLifecycleHook(onBeforeUnmount, beforeDestroy) } if ( destroyed && softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance) ) { registerLifecycleHook(onUnmounted, destroyed) } } if (isArray(expose)) { if (!asMixin) { if (expose.length) { const exposed = instance.exposed || (instance.exposed = proxyRefs({})) expose.forEach(key => { exposed[key] = toRef(publicThis, key as any) }) } else if (!instance.exposed) { instance.exposed = EMPTY_OBJ } } else if (__DEV__) { warn(`The \`expose\` option is ignored when used in mixins.`) } } } function resolveInstanceAssets( instance: ComponentInternalInstance, mixin: ComponentOptions, type: AssetTypes ) { if (mixin[type]) { extend( instance[type] || (instance[type] = extend( {}, (instance.type as ComponentOptions)[type] ) as any), mixin[type] ) } } export function resolveInjections( injectOptions: ComponentInjectOptions, ctx: any, checkDuplicateProperties = NOOP as any ) { if (isArray(injectOptions)) { for (let i = 0; i < injectOptions.length; i++) { const key = injectOptions[i] ctx[key] = inject(key) if (__DEV__) { checkDuplicateProperties!(OptionTypes.INJECT, key) } } } else { for (const key in injectOptions) { const opt = injectOptions[key] if (isObject(opt)) { ctx[key] = inject( opt.from || key, opt.default, true /* treat default function as factory */ ) } else { ctx[key] = inject(opt) } if (__DEV__) { checkDuplicateProperties!(OptionTypes.INJECT, key) } } } } function callSyncHook( name: 'beforeCreate' | 'created', type: LifecycleHooks, options: ComponentOptions, instance: ComponentInternalInstance, globalMixins: ComponentOptions[] ) { for (let i = 0; i < globalMixins.length; i++) { callHookWithMixinAndExtends(name, type, globalMixins[i], instance) } callHookWithMixinAndExtends(name, type, options, instance) } function callHookWithMixinAndExtends( name: 'beforeCreate' | 'created', type: LifecycleHooks, options: ComponentOptions, instance: ComponentInternalInstance ) { const { extends: base, mixins } = options const selfHook = options[name] if (base) { callHookWithMixinAndExtends(name, type, base, instance) } if (mixins) { for (let i = 0; i < mixins.length; i++) { callHookWithMixinAndExtends(name, type, mixins[i], instance) } } if (selfHook) { callWithAsyncErrorHandling( __COMPAT__ && isArray(selfHook) ? selfHook.map(h => h.bind(instance.proxy!)) : selfHook.bind(instance.proxy!), instance, type ) } } function applyMixins( instance: ComponentInternalInstance, mixins: ComponentOptions[], deferredData: DataFn[], deferredWatch: ComponentWatchOptions[], deferredProvide: (Data | Function)[] ) { for (let i = 0; i < mixins.length; i++) { applyOptions( instance, mixins[i], deferredData, deferredWatch, deferredProvide, true ) } } function resolveData( instance: ComponentInternalInstance, dataFn: DataFn, publicThis: ComponentPublicInstance ) { if (__DEV__ && !isFunction(dataFn)) { warn( `The data option must be a function. ` + `Plain object usage is no longer supported.` ) } shouldCacheAccess = false const data = dataFn.call(publicThis, publicThis) shouldCacheAccess = true if (__DEV__ && isPromise(data)) { warn( `data() returned a Promise - note data() cannot be async; If you ` + `intend to perform data fetching before component renders, use ` + `async setup() + .` ) } if (!isObject(data)) { __DEV__ && warn(`data() should return an object.`) } else if (instance.data === EMPTY_OBJ) { instance.data = reactive(data) } else { // existing data: this is a mixin or extends. if ( __COMPAT__ && isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, instance) ) { deepMergeData(instance.data, data, instance) } else { extend(instance.data, data) } } } export function createWatcher( raw: ComponentWatchOptionItem, ctx: Data, publicThis: ComponentPublicInstance, key: string ) { const getter = key.includes('.') ? createPathGetter(publicThis, key) : () => (publicThis as any)[key] if (isString(raw)) { const handler = ctx[raw] if (isFunction(handler)) { watch(getter, handler as WatchCallback) } else if (__DEV__) { warn(`Invalid watch handler specified by key "${raw}"`, handler) } } else if (isFunction(raw)) { watch(getter, raw.bind(publicThis)) } else if (isObject(raw)) { if (isArray(raw)) { raw.forEach(r => createWatcher(r, ctx, publicThis, key)) } else { const handler = isFunction(raw.handler) ? raw.handler.bind(publicThis) : (ctx[raw.handler] as WatchCallback) if (isFunction(handler)) { watch(getter, handler, raw) } else if (__DEV__) { warn(`Invalid watch handler specified by key "${raw.handler}"`, handler) } } } else if (__DEV__) { warn(`Invalid watch option: "${key}"`, raw) } } export function resolveMergedOptions( instance: ComponentInternalInstance ): ComponentOptions { const raw = instance.type as ComponentOptions const { __merged, mixins, extends: extendsOptions } = raw if (__merged) return __merged const globalMixins = instance.appContext.mixins if (!globalMixins.length && !mixins && !extendsOptions) return raw const options = {} globalMixins.forEach(m => mergeOptions(options, m, instance)) mergeOptions(options, raw, instance) return (raw.__merged = options) } export function mergeOptions( to: any, from: any, instance?: ComponentInternalInstance | null, strats = instance && instance.appContext.config.optionMergeStrategies ) { if (__COMPAT__ && isFunction(from)) { from = from.options } const { mixins, extends: extendsOptions } = from extendsOptions && mergeOptions(to, extendsOptions, instance, strats) mixins && mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance, strats) ) for (const key in from) { if (strats && hasOwn(strats, key)) { to[key] = strats[key](to[key], from[key], instance && instance.proxy, key) } else { to[key] = from[key] } } return to }