2019-09-04 10:25:38 +08:00
|
|
|
import {
|
|
|
|
ComponentInstance,
|
|
|
|
Data,
|
|
|
|
ComponentOptions,
|
2019-09-04 23:36:27 +08:00
|
|
|
currentRenderingInstance,
|
2019-09-06 04:09:30 +08:00
|
|
|
currentInstance,
|
|
|
|
ComponentRenderProxy
|
2019-09-04 10:25:38 +08:00
|
|
|
} from './component'
|
|
|
|
import {
|
|
|
|
isFunction,
|
|
|
|
extend,
|
|
|
|
isString,
|
|
|
|
isObject,
|
|
|
|
isArray,
|
2019-09-04 23:36:27 +08:00
|
|
|
EMPTY_OBJ,
|
|
|
|
capitalize,
|
|
|
|
camelize
|
2019-09-04 10:25:38 +08:00
|
|
|
} from '@vue/shared'
|
2019-09-06 04:09:30 +08:00
|
|
|
import { computed } from './apiReactivity'
|
|
|
|
import { watch, WatchOptions, CleanupRegistrator } from './apiWatch'
|
2019-09-04 10:25:38 +08:00
|
|
|
import { provide, inject } from './apiInject'
|
|
|
|
import {
|
|
|
|
onBeforeMount,
|
|
|
|
onMounted,
|
|
|
|
onBeforeUpdate,
|
|
|
|
onUpdated,
|
|
|
|
onErrorCaptured,
|
|
|
|
onRenderTracked,
|
|
|
|
onBeforeUnmount,
|
|
|
|
onUnmounted
|
|
|
|
} from './apiLifecycle'
|
2019-09-05 06:16:11 +08:00
|
|
|
import { DebuggerEvent, reactive } from '@vue/reactivity'
|
2019-09-04 23:36:27 +08:00
|
|
|
import { warn } from './warning'
|
2019-09-04 10:25:38 +08:00
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// TODO legacy component definition also supports constructors with .options
|
|
|
|
type LegacyComponent = ComponentOptions
|
2019-09-04 10:25:38 +08:00
|
|
|
|
2019-09-06 04:09:30 +08:00
|
|
|
export interface ComputedOptions {
|
|
|
|
[key: string]:
|
|
|
|
| Function
|
|
|
|
| {
|
|
|
|
get: Function
|
|
|
|
set: Function
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface MethodOptions {
|
|
|
|
[key: string]: Function
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ExtracComputedReturns<T extends any> = {
|
|
|
|
[key in keyof T]: T[key] extends { get: Function }
|
|
|
|
? ReturnType<T[key]['get']>
|
|
|
|
: ReturnType<T[key]>
|
|
|
|
}
|
|
|
|
|
|
|
|
type WatchHandler = (
|
|
|
|
val: any,
|
|
|
|
oldVal: any,
|
|
|
|
onCleanup: CleanupRegistrator
|
|
|
|
) => void
|
|
|
|
|
2019-09-04 10:25:38 +08:00
|
|
|
// TODO type inference for these options
|
2019-09-06 04:09:30 +08:00
|
|
|
export interface LegacyOptions<
|
|
|
|
Props,
|
|
|
|
RawBindings,
|
|
|
|
D,
|
|
|
|
C extends ComputedOptions,
|
|
|
|
M extends MethodOptions,
|
|
|
|
ThisContext = ThisType<ComponentRenderProxy<Props, D, RawBindings, C, M>>
|
|
|
|
> {
|
2019-09-04 10:25:38 +08:00
|
|
|
el?: any
|
|
|
|
|
|
|
|
// state
|
2019-09-06 04:09:30 +08:00
|
|
|
data?:
|
|
|
|
| D
|
|
|
|
| (<This extends ComponentRenderProxy<Props, {}, RawBindings>>(
|
|
|
|
this: This
|
|
|
|
) => D)
|
|
|
|
computed?: C & ThisContext
|
|
|
|
methods?: M & ThisContext
|
2019-09-04 10:25:38 +08:00
|
|
|
// TODO watch array
|
|
|
|
watch?: Record<
|
|
|
|
string,
|
2019-09-06 04:09:30 +08:00
|
|
|
string | WatchHandler | { handler: WatchHandler } & WatchOptions
|
|
|
|
> &
|
|
|
|
ThisContext
|
|
|
|
provide?:
|
|
|
|
| Data
|
|
|
|
| (<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
|
|
|
|
this: This
|
|
|
|
) => any)
|
2019-09-04 10:25:38 +08:00
|
|
|
inject?:
|
|
|
|
| string[]
|
|
|
|
| Record<
|
|
|
|
string | symbol,
|
2019-09-05 23:11:33 +08:00
|
|
|
string | symbol | { from: string | symbol; default?: any }
|
2019-09-04 10:25:38 +08:00
|
|
|
>
|
|
|
|
|
|
|
|
// composition
|
|
|
|
mixins?: LegacyComponent[]
|
|
|
|
extends?: LegacyComponent
|
|
|
|
|
|
|
|
// lifecycle
|
2019-09-06 04:09:30 +08:00
|
|
|
beforeCreate?(this: ComponentRenderProxy): void
|
|
|
|
created?<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
|
|
|
|
this: This
|
|
|
|
): void
|
2019-09-04 10:25:38 +08:00
|
|
|
beforeMount?(): void
|
|
|
|
mounted?(): void
|
|
|
|
beforeUpdate?(): void
|
|
|
|
updated?(): void
|
|
|
|
activated?(): void
|
|
|
|
decativated?(): void
|
2019-09-05 22:07:43 +08:00
|
|
|
beforeUnmount?(): void
|
|
|
|
unmounted?(): void
|
2019-09-04 10:25:38 +08:00
|
|
|
renderTracked?(e: DebuggerEvent): void
|
|
|
|
renderTriggered?(e: DebuggerEvent): void
|
2019-09-05 01:50:57 +08:00
|
|
|
errorCaptured?(): boolean | void
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
export function applyOptions(
|
|
|
|
instance: ComponentInstance,
|
|
|
|
options: ComponentOptions,
|
|
|
|
asMixin: boolean = false
|
|
|
|
) {
|
2019-09-04 10:25:38 +08:00
|
|
|
const data =
|
2019-09-05 06:16:11 +08:00
|
|
|
instance.data === EMPTY_OBJ ? (instance.data = reactive({})) : instance.data
|
2019-09-04 10:25:38 +08:00
|
|
|
const ctx = instance.renderProxy as any
|
|
|
|
const {
|
2019-09-04 23:36:27 +08:00
|
|
|
// composition
|
|
|
|
mixins,
|
|
|
|
extends: extendsOptions,
|
|
|
|
// 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
|
|
|
// assets
|
|
|
|
components,
|
|
|
|
directives,
|
|
|
|
// lifecycle
|
2019-09-04 10:25:38 +08:00
|
|
|
beforeMount,
|
|
|
|
mounted,
|
|
|
|
beforeUpdate,
|
|
|
|
updated,
|
|
|
|
// TODO activated
|
|
|
|
// TODO decativated
|
2019-09-05 22:07:43 +08:00
|
|
|
beforeUnmount,
|
|
|
|
unmounted,
|
2019-09-04 10:25:38 +08:00
|
|
|
renderTracked,
|
|
|
|
renderTriggered,
|
|
|
|
errorCaptured
|
2019-09-04 23:36:27 +08:00
|
|
|
} = options
|
|
|
|
|
2019-09-05 22:07:43 +08:00
|
|
|
const globalMixins = instance.appContext.mixins
|
|
|
|
|
|
|
|
// beforeCreate
|
|
|
|
if (!asMixin) {
|
|
|
|
callSyncHook('beforeCreate', options, ctx, globalMixins)
|
|
|
|
}
|
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// global mixins are applied first, and only if this is a non-mixin call
|
|
|
|
// so that they are applied once per instance.
|
|
|
|
if (!asMixin) {
|
2019-09-05 22:07:43 +08:00
|
|
|
applyMixins(instance, globalMixins)
|
2019-09-04 23:36:27 +08:00
|
|
|
}
|
|
|
|
// extending a base component...
|
|
|
|
if (extendsOptions) {
|
|
|
|
applyOptions(instance, extendsOptions, true)
|
|
|
|
}
|
|
|
|
// local mixins
|
|
|
|
if (mixins) {
|
|
|
|
applyMixins(instance, mixins)
|
|
|
|
}
|
2019-09-04 10:25:38 +08:00
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// state options
|
2019-09-04 10:25:38 +08:00
|
|
|
if (dataOptions) {
|
|
|
|
extend(data, isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions)
|
|
|
|
}
|
|
|
|
if (computedOptions) {
|
|
|
|
for (const key in computedOptions) {
|
2019-09-06 04:09:30 +08:00
|
|
|
const opt = (computedOptions as ComputedOptions)[key]
|
2019-09-05 06:16:11 +08:00
|
|
|
data[key] = isFunction(opt)
|
|
|
|
? computed(opt.bind(ctx))
|
|
|
|
: computed({
|
|
|
|
get: opt.get.bind(ctx),
|
|
|
|
set: opt.set.bind(ctx)
|
|
|
|
})
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (methods) {
|
|
|
|
for (const key in methods) {
|
2019-09-06 04:09:30 +08:00
|
|
|
data[key] = (methods as MethodOptions)[key].bind(ctx)
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (watchOptions) {
|
|
|
|
for (const key in watchOptions) {
|
|
|
|
const raw = watchOptions[key]
|
|
|
|
const getter = () => ctx[key]
|
|
|
|
if (isString(raw)) {
|
2019-09-05 06:16:11 +08:00
|
|
|
const handler = data[raw]
|
2019-09-04 10:25:38 +08:00
|
|
|
if (isFunction(handler)) {
|
2019-09-05 06:16:11 +08:00
|
|
|
watch(getter, handler as any)
|
2019-09-04 10:25:38 +08:00
|
|
|
} else if (__DEV__) {
|
|
|
|
// TODO warn invalid watch handler path
|
|
|
|
}
|
|
|
|
} else if (isFunction(raw)) {
|
|
|
|
watch(getter, raw.bind(ctx))
|
|
|
|
} else if (isObject(raw)) {
|
2019-09-04 11:04:11 +08:00
|
|
|
// TODO 2.x compat
|
|
|
|
watch(getter, raw.handler.bind(ctx), raw)
|
2019-09-04 10:25:38 +08:00
|
|
|
} else if (__DEV__) {
|
|
|
|
// TODO warn invalid watch options
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (provideOptions) {
|
|
|
|
const provides = isFunction(provideOptions)
|
|
|
|
? provideOptions.call(ctx)
|
|
|
|
: provideOptions
|
|
|
|
for (const key in provides) {
|
|
|
|
provide(key, provides[key])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (injectOptions) {
|
|
|
|
if (isArray(injectOptions)) {
|
|
|
|
for (let i = 0; i < injectOptions.length; i++) {
|
|
|
|
const key = injectOptions[i]
|
|
|
|
data[key] = inject(key)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (const key in injectOptions) {
|
|
|
|
const opt = injectOptions[key]
|
|
|
|
if (isObject(opt)) {
|
|
|
|
data[key] = inject(opt.from, opt.default)
|
|
|
|
} else {
|
|
|
|
data[key] = inject(opt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// asset options
|
|
|
|
if (components) {
|
|
|
|
extend(instance.components, components)
|
|
|
|
}
|
|
|
|
if (directives) {
|
|
|
|
extend(instance.directives, directives)
|
|
|
|
}
|
|
|
|
|
|
|
|
// lifecycle options
|
2019-09-05 22:07:43 +08:00
|
|
|
if (!asMixin) {
|
|
|
|
callSyncHook('created', options, ctx, globalMixins)
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
|
|
|
if (beforeMount) {
|
|
|
|
onBeforeMount(beforeMount.bind(ctx))
|
|
|
|
}
|
|
|
|
if (mounted) {
|
|
|
|
onMounted(mounted.bind(ctx))
|
|
|
|
}
|
|
|
|
if (beforeUpdate) {
|
|
|
|
onBeforeUpdate(beforeUpdate.bind(ctx))
|
|
|
|
}
|
|
|
|
if (updated) {
|
|
|
|
onUpdated(updated.bind(ctx))
|
|
|
|
}
|
|
|
|
if (errorCaptured) {
|
|
|
|
onErrorCaptured(errorCaptured.bind(ctx))
|
|
|
|
}
|
|
|
|
if (renderTracked) {
|
|
|
|
onRenderTracked(renderTracked.bind(ctx))
|
|
|
|
}
|
|
|
|
if (renderTriggered) {
|
|
|
|
onRenderTracked(renderTriggered.bind(ctx))
|
|
|
|
}
|
2019-09-05 22:07:43 +08:00
|
|
|
if (beforeUnmount) {
|
|
|
|
onBeforeUnmount(beforeUnmount.bind(ctx))
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
2019-09-05 22:07:43 +08:00
|
|
|
if (unmounted) {
|
|
|
|
onUnmounted(unmounted.bind(ctx))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function callSyncHook(
|
|
|
|
name: 'beforeCreate' | 'created',
|
|
|
|
options: ComponentOptions,
|
|
|
|
ctx: any,
|
|
|
|
globalMixins: ComponentOptions[]
|
|
|
|
) {
|
|
|
|
callHookFromMixins(name, globalMixins, ctx)
|
|
|
|
const baseHook = options.extends && options.extends[name]
|
|
|
|
if (baseHook) {
|
|
|
|
baseHook.call(ctx)
|
|
|
|
}
|
|
|
|
const mixins = options.mixins
|
|
|
|
if (mixins) {
|
|
|
|
callHookFromMixins(name, mixins, ctx)
|
|
|
|
}
|
|
|
|
const selfHook = options[name]
|
|
|
|
if (selfHook) {
|
|
|
|
selfHook.call(ctx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function callHookFromMixins(
|
|
|
|
name: 'beforeCreate' | 'created',
|
|
|
|
mixins: ComponentOptions[],
|
|
|
|
ctx: any
|
|
|
|
) {
|
|
|
|
for (let i = 0; i < mixins.length; i++) {
|
|
|
|
const fn = mixins[i][name]
|
|
|
|
if (fn) {
|
|
|
|
fn.call(ctx)
|
|
|
|
}
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-04 11:04:11 +08:00
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
function applyMixins(instance: ComponentInstance, mixins: ComponentOptions[]) {
|
|
|
|
for (let i = 0; i < mixins.length; i++) {
|
|
|
|
applyOptions(instance, mixins[i], true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function resolveAsset(type: 'components' | 'directives', name: string) {
|
|
|
|
const instance = currentRenderingInstance || currentInstance
|
|
|
|
if (instance) {
|
|
|
|
let camelized
|
|
|
|
const registry = instance[type]
|
|
|
|
const res =
|
|
|
|
registry[name] ||
|
|
|
|
registry[(camelized = camelize(name))] ||
|
|
|
|
registry[capitalize(camelized)]
|
|
|
|
if (__DEV__ && !res) {
|
|
|
|
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`resolve${capitalize(type.slice(0, -1))} ` +
|
|
|
|
`can only be used in render() or setup().`
|
|
|
|
)
|
|
|
|
}
|
2019-09-04 11:04:11 +08:00
|
|
|
}
|