2a9e9a4096
* chore(runtime-core): extract function isReservedKey * chore: improve code Co-authored-by: Evan You <yyx990803@gmail.com>
1113 lines
28 KiB
TypeScript
1113 lines
28 KiB
TypeScript
import {
|
|
ComponentInternalInstance,
|
|
Data,
|
|
SetupContext,
|
|
ComponentInternalOptions,
|
|
Component,
|
|
ConcreteComponent,
|
|
InternalRenderFunction,
|
|
LifecycleHooks
|
|
} from './component'
|
|
import {
|
|
isFunction,
|
|
extend,
|
|
isString,
|
|
isObject,
|
|
isArray,
|
|
NOOP,
|
|
isPromise,
|
|
LooseRequired,
|
|
UnionToIntersection
|
|
} from '@vue/shared'
|
|
import { isRef, Ref } from '@vue/reactivity'
|
|
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
|
|
} from '@vue/reactivity'
|
|
import {
|
|
ComponentObjectPropsOptions,
|
|
ExtractPropTypes,
|
|
ExtractDefaultPropTypes
|
|
} from './componentProps'
|
|
import { EmitsOptions, EmitsToProps } from './componentEmits'
|
|
import { Directive } from './directives'
|
|
import {
|
|
CreateComponentPublicInstance,
|
|
ComponentPublicInstance,
|
|
isReservedPrefix
|
|
} from './componentPublicInstance'
|
|
import { warn } from './warning'
|
|
import { VNodeChild } from './vnode'
|
|
import { callWithAsyncErrorHandling } from './errorHandling'
|
|
import { deepMergeData } from './compat/data'
|
|
import { DeprecationTypes } from './compat/compatConfig'
|
|
import {
|
|
CompatConfig,
|
|
isCompatEnabled,
|
|
softAssertCompatEnabled
|
|
} from './compat/compatConfig'
|
|
import { OptionMergeFunction } from './apiCreateApp'
|
|
|
|
/**
|
|
* 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> = T extends ComponentOptionsBase<
|
|
infer P, // Props
|
|
any, // RawBindings
|
|
any, // D
|
|
any, // C
|
|
any, // M
|
|
any, // Mixin
|
|
any, // Extends
|
|
any // EmitsOptions
|
|
>
|
|
? 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 = {},
|
|
Provide extends ComponentProvideOptions = ComponentProvideOptions
|
|
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, Provide>,
|
|
ComponentInternalOptions,
|
|
ComponentCustomOptions {
|
|
setup?: (
|
|
this: void,
|
|
props: Readonly<
|
|
LooseRequired<
|
|
Props &
|
|
UnionToIntersection<ExtractOptionProp<Mixin>> &
|
|
UnionToIntersection<ExtractOptionProp<Extends>>
|
|
>
|
|
>,
|
|
ctx: SetupContext<E>
|
|
) => Promise<RawBindings> | 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<string, Component>
|
|
directives?: Record<string, Directive>
|
|
inheritAttrs?: boolean
|
|
emits?: (E | EE[]) & ThisType<void>
|
|
// TODO infer public instance type based on exposed keys
|
|
expose?: string[]
|
|
serverPrefetch?(): Promise<any>
|
|
|
|
// 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<ConcreteComponent>
|
|
/**
|
|
* the inner component resolved by the AsyncComponentWrapper
|
|
* @internal
|
|
*/
|
|
__asyncResolved?: ConcreteComponent
|
|
|
|
// 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,
|
|
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
|
PE = Props & EmitsToProps<E>
|
|
> = ComponentOptionsBase<
|
|
PE,
|
|
RawBindings,
|
|
D,
|
|
C,
|
|
M,
|
|
Mixin,
|
|
Extends,
|
|
E,
|
|
EE,
|
|
{},
|
|
Provide
|
|
> & {
|
|
props?: undefined
|
|
} & ThisType<
|
|
CreateComponentPublicInstance<PE, 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,
|
|
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
|
Props = Readonly<{ [key in PropNames]?: any }> & EmitsToProps<E>
|
|
> = ComponentOptionsBase<
|
|
Props,
|
|
RawBindings,
|
|
D,
|
|
C,
|
|
M,
|
|
Mixin,
|
|
Extends,
|
|
E,
|
|
EE,
|
|
{},
|
|
Provide
|
|
> & {
|
|
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,
|
|
Provide extends ComponentProvideOptions = ComponentProvideOptions,
|
|
Props = Readonly<ExtractPropTypes<PropsOptions>> & EmitsToProps<E>,
|
|
Defaults = ExtractDefaultPropTypes<PropsOptions>
|
|
> = ComponentOptionsBase<
|
|
Props,
|
|
RawBindings,
|
|
D,
|
|
C,
|
|
M,
|
|
Mixin,
|
|
Extends,
|
|
E,
|
|
EE,
|
|
Defaults,
|
|
Provide
|
|
> & {
|
|
props: PropsOptions & ThisType<void>
|
|
} & 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<Props, RawBindings, D, C, M, Mixin, Extends, E> &
|
|
ThisType<
|
|
CreateComponentPublicInstance<
|
|
{},
|
|
RawBindings,
|
|
D,
|
|
C,
|
|
M,
|
|
Mixin,
|
|
Extends,
|
|
E,
|
|
Readonly<Props>
|
|
>
|
|
>
|
|
|
|
export type ComponentOptionsMixin = ComponentOptionsBase<
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any,
|
|
any
|
|
>
|
|
|
|
export type ComputedOptions = Record<
|
|
string,
|
|
ComputedGetter<any> | WritableComputedOptions<any>
|
|
>
|
|
|
|
export interface MethodOptions {
|
|
[key: string]: Function
|
|
}
|
|
|
|
export type ExtractComputedReturns<T extends any> = {
|
|
[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<string, ComponentWatchOptionItem>
|
|
|
|
export type ComponentProvideOptions = ObjectProvideOptions | Function
|
|
|
|
type ObjectProvideOptions = Record<string | symbol, unknown>
|
|
|
|
type ComponentInjectOptions = string[] | ObjectInjectOptions
|
|
|
|
type ObjectInjectOptions = 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,
|
|
Provide extends ComponentProvideOptions = ComponentProvideOptions
|
|
> {
|
|
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?: Provide
|
|
inject?: ComponentInjectOptions
|
|
|
|
// assets
|
|
filters?: Record<string, Function>
|
|
|
|
// 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 `__differentiator`, typescript won't be able to combine different mixins,
|
|
* because the `__differentiator` will be different
|
|
*/
|
|
__differentiator?: keyof D | keyof C | keyof M
|
|
}
|
|
|
|
type MergedHook<T = () => void> = T | T[]
|
|
|
|
export type MergedComponentOptions = ComponentOptions &
|
|
MergedComponentOptionsOverride
|
|
|
|
export type MergedComponentOptionsOverride = {
|
|
beforeCreate?: MergedHook
|
|
created?: MergedHook
|
|
beforeMount?: MergedHook
|
|
mounted?: MergedHook
|
|
beforeUpdate?: MergedHook
|
|
updated?: MergedHook
|
|
activated?: MergedHook
|
|
deactivated?: MergedHook
|
|
/** @deprecated use `beforeUnmount` instead */
|
|
beforeDestroy?: MergedHook
|
|
beforeUnmount?: MergedHook
|
|
/** @deprecated use `unmounted` instead */
|
|
destroyed?: MergedHook
|
|
unmounted?: MergedHook
|
|
renderTracked?: MergedHook<DebuggerHook>
|
|
renderTriggered?: MergedHook<DebuggerHook>
|
|
errorCaptured?: MergedHook<ErrorCapturedHook>
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
export let shouldCacheAccess = true
|
|
|
|
export function applyOptions(instance: ComponentInternalInstance) {
|
|
const options = resolveMergedOptions(instance)
|
|
const publicThis = instance.proxy! as any
|
|
const ctx = instance.ctx
|
|
|
|
// do not cache property access on public proxy during state initialization
|
|
shouldCacheAccess = false
|
|
|
|
// call beforeCreate first before accessing other options since
|
|
// the hook may mutate resolved options (#2791)
|
|
if (options.beforeCreate) {
|
|
callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
|
|
}
|
|
|
|
const {
|
|
// state
|
|
data: dataOptions,
|
|
computed: computedOptions,
|
|
methods,
|
|
watch: watchOptions,
|
|
provide: provideOptions,
|
|
inject: injectOptions,
|
|
// lifecycle
|
|
created,
|
|
beforeMount,
|
|
mounted,
|
|
beforeUpdate,
|
|
updated,
|
|
activated,
|
|
deactivated,
|
|
beforeDestroy,
|
|
beforeUnmount,
|
|
destroyed,
|
|
unmounted,
|
|
render,
|
|
renderTracked,
|
|
renderTriggered,
|
|
errorCaptured,
|
|
serverPrefetch,
|
|
// public API
|
|
expose,
|
|
inheritAttrs,
|
|
// assets
|
|
components,
|
|
directives,
|
|
filters
|
|
} = options
|
|
|
|
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,
|
|
instance.appContext.config.unwrapInjectedRef
|
|
)
|
|
}
|
|
|
|
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 (dataOptions) {
|
|
if (__DEV__ && !isFunction(dataOptions)) {
|
|
warn(
|
|
`The data option must be a function. ` +
|
|
`Plain object usage is no longer supported.`
|
|
)
|
|
}
|
|
const data = dataOptions.call(publicThis, publicThis)
|
|
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() + <Suspense>.`
|
|
)
|
|
}
|
|
if (!isObject(data)) {
|
|
__DEV__ && warn(`data() should return an object.`)
|
|
} else {
|
|
instance.data = reactive(data)
|
|
if (__DEV__) {
|
|
for (const key in data) {
|
|
checkDuplicateProperties!(OptionTypes.DATA, key)
|
|
// expose data on ctx during dev
|
|
if (!isReservedPrefix(key[0])) {
|
|
Object.defineProperty(ctx, key, {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get: () => data[key],
|
|
set: NOOP
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// state initialization complete at this point - start caching access
|
|
shouldCacheAccess = true
|
|
|
|
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) {
|
|
for (const key in watchOptions) {
|
|
createWatcher(watchOptions[key], ctx, publicThis, key)
|
|
}
|
|
}
|
|
|
|
if (provideOptions) {
|
|
const provides = isFunction(provideOptions)
|
|
? provideOptions.call(publicThis)
|
|
: provideOptions
|
|
Reflect.ownKeys(provides).forEach(key => {
|
|
provide(key, provides[key])
|
|
})
|
|
}
|
|
|
|
if (created) {
|
|
callHook(created, instance, LifecycleHooks.CREATED)
|
|
}
|
|
|
|
function registerLifecycleHook(
|
|
register: Function,
|
|
hook?: Function | Function[]
|
|
) {
|
|
if (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 (expose.length) {
|
|
const exposed = instance.exposed || (instance.exposed = {})
|
|
expose.forEach(key => {
|
|
Object.defineProperty(exposed, key, {
|
|
get: () => publicThis[key],
|
|
set: val => (publicThis[key] = val)
|
|
})
|
|
})
|
|
} else if (!instance.exposed) {
|
|
instance.exposed = {}
|
|
}
|
|
}
|
|
|
|
// options that are handled when creating the instance but also need to be
|
|
// applied from mixins
|
|
if (render && instance.render === NOOP) {
|
|
instance.render = render as InternalRenderFunction
|
|
}
|
|
if (inheritAttrs != null) {
|
|
instance.inheritAttrs = inheritAttrs
|
|
}
|
|
|
|
// asset options.
|
|
if (components) instance.components = components as any
|
|
if (directives) instance.directives = directives
|
|
if (
|
|
__COMPAT__ &&
|
|
filters &&
|
|
isCompatEnabled(DeprecationTypes.FILTERS, instance)
|
|
) {
|
|
instance.filters = filters
|
|
}
|
|
}
|
|
|
|
export function resolveInjections(
|
|
injectOptions: ComponentInjectOptions,
|
|
ctx: any,
|
|
checkDuplicateProperties = NOOP as any,
|
|
unwrapRef = false
|
|
) {
|
|
if (isArray(injectOptions)) {
|
|
injectOptions = normalizeInject(injectOptions)!
|
|
}
|
|
for (const key in injectOptions) {
|
|
const opt = (injectOptions as ObjectInjectOptions)[key]
|
|
let injected: unknown
|
|
if (isObject(opt)) {
|
|
if ('default' in opt) {
|
|
injected = inject(
|
|
opt.from || key,
|
|
opt.default,
|
|
true /* treat default function as factory */
|
|
)
|
|
} else {
|
|
injected = inject(opt.from || key)
|
|
}
|
|
} else {
|
|
injected = inject(opt)
|
|
}
|
|
if (isRef(injected)) {
|
|
// TODO remove the check in 3.3
|
|
if (unwrapRef) {
|
|
Object.defineProperty(ctx, key, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: () => (injected as Ref).value,
|
|
set: v => ((injected as Ref).value = v)
|
|
})
|
|
} else {
|
|
if (__DEV__) {
|
|
warn(
|
|
`injected property "${key}" is a ref and will be auto-unwrapped ` +
|
|
`and no longer needs \`.value\` in the next minor release. ` +
|
|
`To opt-in to the new behavior now, ` +
|
|
`set \`app.config.unwrapInjectedRef = true\` (this config is ` +
|
|
`temporary and will not be needed in the future.)`
|
|
)
|
|
}
|
|
ctx[key] = injected
|
|
}
|
|
} else {
|
|
ctx[key] = injected
|
|
}
|
|
if (__DEV__) {
|
|
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
function callHook(
|
|
hook: Function,
|
|
instance: ComponentInternalInstance,
|
|
type: LifecycleHooks
|
|
) {
|
|
callWithAsyncErrorHandling(
|
|
isArray(hook)
|
|
? hook.map(h => h.bind(instance.proxy!))
|
|
: hook.bind(instance.proxy!),
|
|
instance,
|
|
type
|
|
)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve merged options and cache it on the component.
|
|
* This is done only once per-component since the merging does not involve
|
|
* instances.
|
|
*/
|
|
export function resolveMergedOptions(
|
|
instance: ComponentInternalInstance
|
|
): MergedComponentOptions {
|
|
const base = instance.type as ComponentOptions
|
|
const { mixins, extends: extendsOptions } = base
|
|
const {
|
|
mixins: globalMixins,
|
|
optionsCache: cache,
|
|
config: { optionMergeStrategies }
|
|
} = instance.appContext
|
|
const cached = cache.get(base)
|
|
|
|
let resolved: MergedComponentOptions
|
|
|
|
if (cached) {
|
|
resolved = cached
|
|
} else if (!globalMixins.length && !mixins && !extendsOptions) {
|
|
if (
|
|
__COMPAT__ &&
|
|
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
|
|
) {
|
|
resolved = extend({}, base) as MergedComponentOptions
|
|
resolved.parent = instance.parent && instance.parent.proxy
|
|
resolved.propsData = instance.vnode.props
|
|
} else {
|
|
resolved = base as MergedComponentOptions
|
|
}
|
|
} else {
|
|
resolved = {}
|
|
if (globalMixins.length) {
|
|
globalMixins.forEach(m =>
|
|
mergeOptions(resolved, m, optionMergeStrategies, true)
|
|
)
|
|
}
|
|
mergeOptions(resolved, base, optionMergeStrategies)
|
|
}
|
|
|
|
cache.set(base, resolved)
|
|
return resolved
|
|
}
|
|
|
|
export function mergeOptions(
|
|
to: any,
|
|
from: any,
|
|
strats: Record<string, OptionMergeFunction>,
|
|
asMixin = false
|
|
) {
|
|
if (__COMPAT__ && isFunction(from)) {
|
|
from = from.options
|
|
}
|
|
|
|
const { mixins, extends: extendsOptions } = from
|
|
|
|
if (extendsOptions) {
|
|
mergeOptions(to, extendsOptions, strats, true)
|
|
}
|
|
if (mixins) {
|
|
mixins.forEach((m: ComponentOptionsMixin) =>
|
|
mergeOptions(to, m, strats, true)
|
|
)
|
|
}
|
|
|
|
for (const key in from) {
|
|
if (asMixin && key === 'expose') {
|
|
__DEV__ &&
|
|
warn(
|
|
`"expose" option is ignored when declared in mixins or extends. ` +
|
|
`It should only be declared in the base component itself.`
|
|
)
|
|
} else {
|
|
const strat = internalOptionMergeStrats[key] || (strats && strats[key])
|
|
to[key] = strat ? strat(to[key], from[key]) : from[key]
|
|
}
|
|
}
|
|
return to
|
|
}
|
|
|
|
export const internalOptionMergeStrats: Record<string, Function> = {
|
|
data: mergeDataFn,
|
|
props: mergeObjectOptions, // TODO
|
|
emits: mergeObjectOptions, // TODO
|
|
// objects
|
|
methods: mergeObjectOptions,
|
|
computed: mergeObjectOptions,
|
|
// lifecycle
|
|
beforeCreate: mergeAsArray,
|
|
created: mergeAsArray,
|
|
beforeMount: mergeAsArray,
|
|
mounted: mergeAsArray,
|
|
beforeUpdate: mergeAsArray,
|
|
updated: mergeAsArray,
|
|
beforeDestroy: mergeAsArray,
|
|
beforeUnmount: mergeAsArray,
|
|
destroyed: mergeAsArray,
|
|
unmounted: mergeAsArray,
|
|
activated: mergeAsArray,
|
|
deactivated: mergeAsArray,
|
|
errorCaptured: mergeAsArray,
|
|
serverPrefetch: mergeAsArray,
|
|
// assets
|
|
components: mergeObjectOptions,
|
|
directives: mergeObjectOptions,
|
|
// watch
|
|
watch: mergeWatchOptions,
|
|
// provide / inject
|
|
provide: mergeDataFn,
|
|
inject: mergeInject
|
|
}
|
|
|
|
if (__COMPAT__) {
|
|
internalOptionMergeStrats.filters = mergeObjectOptions
|
|
}
|
|
|
|
function mergeDataFn(to: any, from: any) {
|
|
if (!from) {
|
|
return to
|
|
}
|
|
if (!to) {
|
|
return from
|
|
}
|
|
return function mergedDataFn(this: ComponentPublicInstance) {
|
|
return (
|
|
__COMPAT__ && isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, null)
|
|
? deepMergeData
|
|
: extend
|
|
)(
|
|
isFunction(to) ? to.call(this, this) : to,
|
|
isFunction(from) ? from.call(this, this) : from
|
|
)
|
|
}
|
|
}
|
|
|
|
function mergeInject(
|
|
to: ComponentInjectOptions | undefined,
|
|
from: ComponentInjectOptions
|
|
) {
|
|
return mergeObjectOptions(normalizeInject(to), normalizeInject(from))
|
|
}
|
|
|
|
function normalizeInject(
|
|
raw: ComponentInjectOptions | undefined
|
|
): ObjectInjectOptions | undefined {
|
|
if (isArray(raw)) {
|
|
const res: ObjectInjectOptions = {}
|
|
for (let i = 0; i < raw.length; i++) {
|
|
res[raw[i]] = raw[i]
|
|
}
|
|
return res
|
|
}
|
|
return raw
|
|
}
|
|
|
|
function mergeAsArray<T = Function>(to: T[] | T | undefined, from: T | T[]) {
|
|
return to ? [...new Set([].concat(to as any, from as any))] : from
|
|
}
|
|
|
|
function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
|
|
return to ? extend(extend(Object.create(null), to), from) : from
|
|
}
|
|
|
|
function mergeWatchOptions(
|
|
to: ComponentWatchOptions | undefined,
|
|
from: ComponentWatchOptions | undefined
|
|
) {
|
|
if (!to) return from
|
|
if (!from) return to
|
|
const merged = extend(Object.create(null), to)
|
|
for (const key in from) {
|
|
merged[key] = mergeAsArray(to[key], from[key])
|
|
}
|
|
return merged
|
|
}
|