vue3-yuanma/packages/runtime-core/src/componentOptions.ts

1113 lines
28 KiB
TypeScript
Raw Normal View History

2019-09-04 10:25:38 +08:00
import {
2019-09-07 00:58:31 +08:00
ComponentInternalInstance,
2019-09-04 10:25:38 +08:00
Data,
2019-10-22 01:44:01 +08:00
SetupContext,
ComponentInternalOptions,
Component,
ConcreteComponent,
InternalRenderFunction,
LifecycleHooks
2019-09-04 10:25:38 +08:00
} from './component'
import {
isFunction,
extend,
isString,
isObject,
isArray,
NOOP,
2022-01-16 15:43:19 +08:00
isPromise,
LooseRequired,
UnionToIntersection
2019-09-04 10:25:38 +08:00
} from '@vue/shared'
import { isRef, Ref } from '@vue/reactivity'
import { computed } from './apiComputed'
import {
watch,
WatchOptions,
WatchCallback,
createPathGetter
} from './apiWatch'
2019-09-04 10:25:38 +08:00
import { provide, inject } from './apiInject'
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onErrorCaptured,
onRenderTracked,
onBeforeUnmount,
onUnmounted,
2019-10-30 10:28:38 +08:00
onActivated,
onDeactivated,
2019-10-15 11:15:09 +08:00
onRenderTriggered,
DebuggerHook,
ErrorCapturedHook,
onServerPrefetch
2019-09-04 10:25:38 +08:00
} from './apiLifecycle'
2019-10-22 01:57:20 +08:00
import {
reactive,
ComputedGetter,
WritableComputedOptions
2019-10-22 01:57:20 +08:00
} 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'
2021-04-06 21:31:47 +08:00
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
: {}
2019-10-22 23:26:48 +08:00
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?: (
2020-01-11 00:46:34 +08:00
this: void,
props: Readonly<
LooseRequired<
Props &
UnionToIntersection<ExtractOptionProp<Mixin>> &
UnionToIntersection<ExtractOptionProp<Extends>>
>
>,
ctx: SetupContext<E>
) => Promise<RawBindings> | RawBindings | RenderFunction | void
name?: string
2019-12-11 23:39:29 +08:00
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[]
2020-09-02 10:52:46 +08:00
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,
2020-07-11 10:12:25 +08:00
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!
2019-11-28 17:01:53 +08:00
// 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
>
>
2019-10-08 21:26:09 +08:00
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>
>
>
2019-09-04 10:25:38 +08:00
export type ComponentOptionsMixin = ComponentOptionsBase<
any,
any,
any,
any,
any,
any,
any,
any,
any,
any
>
2019-10-22 01:57:20 +08:00
export type ComputedOptions = Record<
string,
ComputedGetter<any> | WritableComputedOptions<any>
>
2019-09-06 04:09:30 +08:00
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
2019-09-06 04:09:30 +08:00
}
2021-04-22 21:49:25 +08:00
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<
2019-09-06 04:09:30 +08:00
Props,
D,
C extends ComputedOptions,
M extends MethodOptions,
Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin,
Provide extends ComponentProvideOptions = ComponentProvideOptions
2019-09-06 04:09:30 +08:00
> {
compatConfig?: CompatConfig
// allow any custom options
[key: string]: any
2019-09-04 10:25:38 +08:00
// 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
2019-09-04 10:25:38 +08:00
2021-04-20 00:08:26 +08:00
// assets
filters?: Record<string, Function>
2019-09-04 10:25:38 +08:00
// composition
mixins?: Mixin[]
extends?: Extends
2019-09-04 10:25:38 +08:00
// lifecycle
beforeCreate?(): void
created?(): void
2019-09-04 10:25:38 +08:00
beforeMount?(): void
mounted?(): void
beforeUpdate?(): void
updated?(): void
activated?(): void
2019-10-05 22:48:54 +08:00
deactivated?(): void
/** @deprecated use `beforeUnmount` instead */
beforeDestroy?(): void
2019-09-05 22:07:43 +08:00
beforeUnmount?(): void
/** @deprecated use `unmounted` instead */
destroyed?(): void
2019-09-05 22:07:43 +08:00
unmounted?(): void
2019-10-15 11:15:09 +08:00
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,
2022-05-10 10:51:51 +08:00
* 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
2019-09-04 10:25:38 +08:00
}
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)
2021-04-23 02:59:54 +08:00
}
2019-09-04 10:25:38 +08:00
const {
2019-09-04 23:36:27 +08:00
// state
2019-09-04 10:25:38 +08:00
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
2019-09-04 23:36:27 +08:00
// lifecycle
created,
2019-09-04 10:25:38 +08:00
beforeMount,
mounted,
beforeUpdate,
updated,
2019-10-30 10:28:38 +08:00
activated,
deactivated,
beforeDestroy,
2019-09-05 22:07:43 +08:00
beforeUnmount,
destroyed,
2019-09-05 22:07:43 +08:00
unmounted,
render,
2019-09-04 10:25:38 +08:00
renderTracked,
renderTriggered,
errorCaptured,
serverPrefetch,
// public API
expose,
inheritAttrs,
// assets
components,
directives,
filters
2019-09-04 23:36:27 +08:00
} = 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
})
}
}
}
}
2019-09-04 10:25:38 +08:00
}
// state initialization complete at this point - start caching access
shouldCacheAccess = true
2019-09-04 10:25:38 +08:00
if (computedOptions) {
for (const key in computedOptions) {
2019-09-06 04:09:30 +08:00
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)
}
2019-09-04 10:25:38 +08:00
}
}
2019-09-04 10:25:38 +08:00
if (watchOptions) {
for (const key in watchOptions) {
createWatcher(watchOptions[key], ctx, publicThis, key)
}
2019-09-04 10:25:38 +08:00
}
2019-09-04 10:25:38 +08:00
if (provideOptions) {
const provides = isFunction(provideOptions)
? provideOptions.call(publicThis)
: provideOptions
Reflect.ownKeys(provides).forEach(key => {
provide(key, provides[key])
})
2019-09-04 10:25:38 +08:00
}
if (created) {
callHook(created, instance, LifecycleHooks.CREATED)
2019-09-04 10:25:38 +08:00
}
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)
2021-04-06 22:07:16 +08:00
if (__COMPAT__) {
if (
beforeDestroy &&
2021-04-10 06:52:14 +08:00
softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
) {
registerLifecycleHook(onBeforeUnmount, beforeDestroy)
2021-04-06 22:07:16 +08:00
}
if (
destroyed &&
2021-04-10 06:52:14 +08:00
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
) {
registerLifecycleHook(onUnmounted, destroyed)
2021-04-06 22:07:16 +08:00
}
}
2020-11-17 00:28:37 +08:00
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 = {}
2020-11-17 00:28:37 +08:00
}
}
// 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
}
2019-09-05 22:07:43 +08:00
// asset options.
if (components) instance.components = components as any
if (directives) instance.directives = directives
if (
__COMPAT__ &&
filters &&
isCompatEnabled(DeprecationTypes.FILTERS, instance)
) {
instance.filters = filters
2021-04-20 00:08:26 +08:00
}
}
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)
2019-09-05 22:07:43 +08:00
}
2019-09-04 10:25:38 +08:00
}
}
2019-09-04 11:04:11 +08:00
function callHook(
hook: Function,
2019-09-07 00:58:31 +08:00
instance: ComponentInternalInstance,
type: LifecycleHooks
2019-09-07 00:58:31 +08:00
) {
callWithAsyncErrorHandling(
isArray(hook)
? hook.map(h => h.bind(instance.proxy!))
: hook.bind(instance.proxy!),
instance,
type
)
2019-09-04 23:36:27 +08:00
}
2021-04-22 21:49:25 +08:00
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)) {
2019-12-31 00:30:12 +08:00
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
}
2021-04-12 04:53:43 +08:00
export function mergeOptions(
to: any,
from: any,
strats: Record<string, OptionMergeFunction>,
asMixin = false
2021-04-12 04:53:43 +08:00
) {
2021-04-23 02:59:54 +08:00
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]
}
}
2021-04-23 02:59:54 +08:00
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
}