fix(runtime-core): align option merge behavior with Vue 2
fix #3566, #2791
This commit is contained in:
parent
1e35a860b9
commit
e2ca67b59a
@ -1066,6 +1066,188 @@ describe('api: options', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('options merge strategies', () => {
|
||||||
|
test('this.$options.data', () => {
|
||||||
|
const mixin = {
|
||||||
|
data() {
|
||||||
|
return { foo: 1, bar: 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
foo: 3,
|
||||||
|
baz: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
expect(this.$options.data).toBeInstanceOf(Function)
|
||||||
|
expect(this.$options.data()).toEqual({
|
||||||
|
foo: 3,
|
||||||
|
bar: 2,
|
||||||
|
baz: 4
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options.inject', () => {
|
||||||
|
const mixin = {
|
||||||
|
inject: ['a']
|
||||||
|
}
|
||||||
|
const app = createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
inject: { b: 'b', c: { from: 'd' } },
|
||||||
|
created() {
|
||||||
|
expect(this.$options.inject.a).toEqual('a')
|
||||||
|
expect(this.$options.inject.b).toEqual('b')
|
||||||
|
expect(this.$options.inject.c).toEqual({ from: 'd' })
|
||||||
|
expect(this.a).toBe(1)
|
||||||
|
expect(this.b).toBe(2)
|
||||||
|
expect(this.c).toBe(3)
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
})
|
||||||
|
|
||||||
|
app.provide('a', 1)
|
||||||
|
app.provide('b', 2)
|
||||||
|
app.provide('d', 3)
|
||||||
|
app.mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options.provide', () => {
|
||||||
|
const mixin = {
|
||||||
|
provide: {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
expect(this.$options.provide).toBeInstanceOf(Function)
|
||||||
|
expect(this.$options.provide()).toEqual({ a: 1, b: 2 })
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options[lifecycle-name]', () => {
|
||||||
|
const mixin = {
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
mounted() {},
|
||||||
|
created() {
|
||||||
|
expect(this.$options.mounted).toBeInstanceOf(Array)
|
||||||
|
expect(this.$options.mounted.length).toBe(2)
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options[asset-name]', () => {
|
||||||
|
const mixin = {
|
||||||
|
components: {
|
||||||
|
a: {}
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
d1: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
components: {
|
||||||
|
b: {}
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
d2: {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
expect('a' in this.$options.components).toBe(true)
|
||||||
|
expect('b' in this.$options.components).toBe(true)
|
||||||
|
expect('d1' in this.$options.directives).toBe(true)
|
||||||
|
expect('d2' in this.$options.directives).toBe(true)
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options.methods', () => {
|
||||||
|
const mixin = {
|
||||||
|
methods: {
|
||||||
|
fn1() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
methods: {
|
||||||
|
fn2() {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
expect(this.$options.methods.fn1).toBeInstanceOf(Function)
|
||||||
|
expect(this.$options.methods.fn2).toBeInstanceOf(Function)
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('this.$options.computed', () => {
|
||||||
|
const mixin = {
|
||||||
|
computed: {
|
||||||
|
c1() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
computed: {
|
||||||
|
c2() {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
expect(this.$options.computed.c1).toBeInstanceOf(Function)
|
||||||
|
expect(this.$options.computed.c2).toBeInstanceOf(Function)
|
||||||
|
},
|
||||||
|
render: () => null
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
})
|
||||||
|
|
||||||
|
// #2791
|
||||||
|
test('modify $options in the beforeCreate hook', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const mixin = {
|
||||||
|
data() {
|
||||||
|
return { foo: 1 }
|
||||||
|
},
|
||||||
|
beforeCreate(this: any) {
|
||||||
|
if (!this.$options.computed) {
|
||||||
|
this.$options.computed = {}
|
||||||
|
}
|
||||||
|
this.$options.computed.value = () => count.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
createApp({
|
||||||
|
mixins: [mixin],
|
||||||
|
render(this: any) {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe('0')
|
||||||
|
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe('1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('warnings', () => {
|
describe('warnings', () => {
|
||||||
test('Expected a function as watch handler', () => {
|
test('Expected a function as watch handler', () => {
|
||||||
const Comp = {
|
const Comp = {
|
||||||
|
@ -4,7 +4,11 @@ import {
|
|||||||
validateComponentName,
|
validateComponentName,
|
||||||
Component
|
Component
|
||||||
} from './component'
|
} from './component'
|
||||||
import { ComponentOptions, RuntimeCompilerOptions } from './componentOptions'
|
import {
|
||||||
|
ComponentOptions,
|
||||||
|
MergedComponentOptions,
|
||||||
|
RuntimeCompilerOptions
|
||||||
|
} from './componentOptions'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstance'
|
import { ComponentPublicInstance } from './componentPublicInstance'
|
||||||
import { Directive, validateDirectiveName } from './directives'
|
import { Directive, validateDirectiveName } from './directives'
|
||||||
import { RootRenderFunction } from './renderer'
|
import { RootRenderFunction } from './renderer'
|
||||||
@ -98,7 +102,7 @@ export interface AppContext {
|
|||||||
* Each app instance has its own cache because app-level global mixins and
|
* Each app instance has its own cache because app-level global mixins and
|
||||||
* optionMergeStrategies can affect merge behavior.
|
* optionMergeStrategies can affect merge behavior.
|
||||||
*/
|
*/
|
||||||
cache: WeakMap<ComponentOptions, ComponentOptions>
|
cache: WeakMap<ComponentOptions, MergedComponentOptions>
|
||||||
/**
|
/**
|
||||||
* Flag for de-optimizing props normalization
|
* Flag for de-optimizing props normalization
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -531,7 +531,10 @@ const seenConfigObjects = /*#__PURE__*/ new WeakSet<CompatConfig>()
|
|||||||
const warnedInvalidKeys: Record<string, boolean> = {}
|
const warnedInvalidKeys: Record<string, boolean> = {}
|
||||||
|
|
||||||
// dev only
|
// dev only
|
||||||
export function validateCompatConfig(config: CompatConfig) {
|
export function validateCompatConfig(
|
||||||
|
config: CompatConfig,
|
||||||
|
instance?: ComponentInternalInstance
|
||||||
|
) {
|
||||||
if (seenConfigObjects.has(config)) {
|
if (seenConfigObjects.has(config)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -558,6 +561,14 @@ export function validateCompatConfig(config: CompatConfig) {
|
|||||||
warnedInvalidKeys[key] = true
|
warnedInvalidKeys[key] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instance && config[DeprecationTypes.OPTIONS_DATA_MERGE] != null) {
|
||||||
|
warn(
|
||||||
|
`Deprecation config "${
|
||||||
|
DeprecationTypes.OPTIONS_DATA_MERGE
|
||||||
|
}" can only be configured globally.`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCompatConfigForKey(
|
export function getCompatConfigForKey(
|
||||||
|
@ -1,39 +1,16 @@
|
|||||||
import { isFunction, isPlainObject } from '@vue/shared'
|
import { isPlainObject } from '@vue/shared'
|
||||||
import { ComponentInternalInstance } from '../component'
|
|
||||||
import { ComponentPublicInstance } from '../componentPublicInstance'
|
|
||||||
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
import { DeprecationTypes, warnDeprecation } from './compatConfig'
|
||||||
|
|
||||||
export function deepMergeData(
|
export function deepMergeData(to: any, from: any) {
|
||||||
to: any,
|
|
||||||
from: any,
|
|
||||||
instance: ComponentInternalInstance
|
|
||||||
) {
|
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
const toVal = to[key]
|
const toVal = to[key]
|
||||||
const fromVal = from[key]
|
const fromVal = from[key]
|
||||||
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
|
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
|
||||||
__DEV__ &&
|
__DEV__ && warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, null, key)
|
||||||
warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
|
deepMergeData(toVal, fromVal)
|
||||||
deepMergeData(toVal, fromVal, instance)
|
|
||||||
} else {
|
} else {
|
||||||
to[key] = fromVal
|
to[key] = fromVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeDataOption(to: any, from: any) {
|
|
||||||
if (!from) {
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
if (!to) {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
return function mergedDataFn(this: ComponentPublicInstance) {
|
|
||||||
return deepMergeData(
|
|
||||||
isFunction(to) ? to.call(this, this) : to,
|
|
||||||
isFunction(from) ? from.call(this, this) : from,
|
|
||||||
this.$
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -34,7 +34,11 @@ import {
|
|||||||
isRuntimeOnly,
|
isRuntimeOnly,
|
||||||
setupComponent
|
setupComponent
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { RenderFunction, mergeOptions } from '../componentOptions'
|
import {
|
||||||
|
RenderFunction,
|
||||||
|
mergeOptions,
|
||||||
|
internalOptionMergeStrats
|
||||||
|
} from '../componentOptions'
|
||||||
import { ComponentPublicInstance } from '../componentPublicInstance'
|
import { ComponentPublicInstance } from '../componentPublicInstance'
|
||||||
import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
|
import { devtoolsInitApp, devtoolsUnmountApp } from '../devtools'
|
||||||
import { Directive } from '../directives'
|
import { Directive } from '../directives'
|
||||||
@ -43,8 +47,7 @@ import { version } from '..'
|
|||||||
import {
|
import {
|
||||||
installLegacyConfigWarnings,
|
installLegacyConfigWarnings,
|
||||||
installLegacyOptionMergeStrats,
|
installLegacyOptionMergeStrats,
|
||||||
LegacyConfig,
|
LegacyConfig
|
||||||
legacyOptionMergeStrats
|
|
||||||
} from './globalConfig'
|
} from './globalConfig'
|
||||||
import { LegacyDirective } from './customDirective'
|
import { LegacyDirective } from './customDirective'
|
||||||
import {
|
import {
|
||||||
@ -231,8 +234,7 @@ export function createCompatVue(
|
|||||||
mergeOptions(
|
mergeOptions(
|
||||||
extend({}, SubVue.options),
|
extend({}, SubVue.options),
|
||||||
inlineOptions,
|
inlineOptions,
|
||||||
null,
|
internalOptionMergeStrats as any
|
||||||
legacyOptionMergeStrats as any
|
|
||||||
),
|
),
|
||||||
SubVue
|
SubVue
|
||||||
)
|
)
|
||||||
@ -257,8 +259,7 @@ export function createCompatVue(
|
|||||||
SubVue.options = mergeOptions(
|
SubVue.options = mergeOptions(
|
||||||
mergeBase,
|
mergeBase,
|
||||||
extendOptions,
|
extendOptions,
|
||||||
null,
|
internalOptionMergeStrats as any
|
||||||
legacyOptionMergeStrats as any
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SubVue.options._base = SubVue
|
SubVue.options._base = SubVue
|
||||||
@ -305,8 +306,7 @@ export function createCompatVue(
|
|||||||
mergeOptions(
|
mergeOptions(
|
||||||
parent,
|
parent,
|
||||||
child,
|
child,
|
||||||
vm && vm.$,
|
vm ? undefined : (internalOptionMergeStrats as any)
|
||||||
vm ? undefined : (legacyOptionMergeStrats as any)
|
|
||||||
),
|
),
|
||||||
defineReactive
|
defineReactive
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { extend, isArray } from '@vue/shared'
|
|
||||||
import { AppConfig } from '../apiCreateApp'
|
import { AppConfig } from '../apiCreateApp'
|
||||||
import { mergeDataOption } from './data'
|
|
||||||
import {
|
import {
|
||||||
DeprecationTypes,
|
DeprecationTypes,
|
||||||
softAssertCompatEnabled,
|
softAssertCompatEnabled,
|
||||||
warnDeprecation
|
warnDeprecation
|
||||||
} from './compatConfig'
|
} from './compatConfig'
|
||||||
import { isCopyingConfig } from './global'
|
import { isCopyingConfig } from './global'
|
||||||
|
import { internalOptionMergeStrats } from '../componentOptions'
|
||||||
|
|
||||||
// legacy config warnings
|
// legacy config warnings
|
||||||
export type LegacyConfig = {
|
export type LegacyConfig = {
|
||||||
@ -70,60 +69,16 @@ export function installLegacyOptionMergeStrats(config: AppConfig) {
|
|||||||
return target[key]
|
return target[key]
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
key in legacyOptionMergeStrats &&
|
key in internalOptionMergeStrats &&
|
||||||
softAssertCompatEnabled(
|
softAssertCompatEnabled(
|
||||||
DeprecationTypes.CONFIG_OPTION_MERGE_STRATS,
|
DeprecationTypes.CONFIG_OPTION_MERGE_STRATS,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return legacyOptionMergeStrats[
|
return internalOptionMergeStrats[
|
||||||
key as keyof typeof legacyOptionMergeStrats
|
key as keyof typeof internalOptionMergeStrats
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const legacyOptionMergeStrats = {
|
|
||||||
data: mergeDataOption,
|
|
||||||
beforeCreate: mergeHook,
|
|
||||||
created: mergeHook,
|
|
||||||
beforeMount: mergeHook,
|
|
||||||
mounted: mergeHook,
|
|
||||||
beforeUpdate: mergeHook,
|
|
||||||
updated: mergeHook,
|
|
||||||
beforeDestroy: mergeHook,
|
|
||||||
destroyed: mergeHook,
|
|
||||||
activated: mergeHook,
|
|
||||||
deactivated: mergeHook,
|
|
||||||
errorCaptured: mergeHook,
|
|
||||||
serverPrefetch: mergeHook,
|
|
||||||
// assets
|
|
||||||
components: mergeObjectOptions,
|
|
||||||
directives: mergeObjectOptions,
|
|
||||||
filters: mergeObjectOptions,
|
|
||||||
// objects
|
|
||||||
props: mergeObjectOptions,
|
|
||||||
methods: mergeObjectOptions,
|
|
||||||
inject: mergeObjectOptions,
|
|
||||||
computed: mergeObjectOptions,
|
|
||||||
// watch has special merge behavior in v2, but isn't actually needed in v3.
|
|
||||||
// since we are only exposing these for compat and nobody should be relying
|
|
||||||
// on the watch-specific behavior, just expose the object merge strat.
|
|
||||||
watch: mergeObjectOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
function toArray(target: any) {
|
|
||||||
return isArray(target) ? target : target ? [target] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeHook(
|
|
||||||
to: Function[] | Function | undefined,
|
|
||||||
from: Function | Function[]
|
|
||||||
) {
|
|
||||||
return Array.from(new Set([...toArray(to), ...toArray(from)]))
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
|
|
||||||
return to ? extend(extend(Object.create(null), to), from) : from
|
|
||||||
}
|
|
||||||
|
@ -795,7 +795,7 @@ export function finishComponentSetup(
|
|||||||
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
|
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
applyOptions(instance, Component)
|
applyOptions(instance)
|
||||||
resetTracking()
|
resetTracking()
|
||||||
currentInstance = null
|
currentInstance = null
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
isArray,
|
isArray,
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
NOOP,
|
NOOP,
|
||||||
hasOwn,
|
|
||||||
isPromise
|
isPromise
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { computed } from './apiComputed'
|
import { computed } from './apiComputed'
|
||||||
@ -47,7 +46,6 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
ComputedGetter,
|
ComputedGetter,
|
||||||
WritableComputedOptions,
|
WritableComputedOptions,
|
||||||
toRaw,
|
|
||||||
proxyRefs,
|
proxyRefs,
|
||||||
toRef
|
toRef
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
@ -73,12 +71,6 @@ import {
|
|||||||
isCompatEnabled,
|
isCompatEnabled,
|
||||||
softAssertCompatEnabled
|
softAssertCompatEnabled
|
||||||
} from './compat/compatConfig'
|
} from './compat/compatConfig'
|
||||||
import {
|
|
||||||
AssetTypes,
|
|
||||||
COMPONENTS,
|
|
||||||
DIRECTIVES,
|
|
||||||
FILTERS
|
|
||||||
} from './helpers/resolveAssets'
|
|
||||||
import { OptionMergeFunction } from './apiCreateApp'
|
import { OptionMergeFunction } from './apiCreateApp'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -389,12 +381,12 @@ type ComponentWatchOptionItem = WatchOptionItem | WatchOptionItem[]
|
|||||||
|
|
||||||
type ComponentWatchOptions = Record<string, ComponentWatchOptionItem>
|
type ComponentWatchOptions = Record<string, ComponentWatchOptionItem>
|
||||||
|
|
||||||
type ComponentInjectOptions =
|
type ComponentInjectOptions = string[] | ObjectInjectOptions
|
||||||
| string[]
|
|
||||||
| Record<
|
type ObjectInjectOptions = Record<
|
||||||
string | symbol,
|
string | symbol,
|
||||||
string | symbol | { from?: string | symbol; default?: unknown }
|
string | symbol | { from?: string | symbol; default?: unknown }
|
||||||
>
|
>
|
||||||
|
|
||||||
interface LegacyOptions<
|
interface LegacyOptions<
|
||||||
Props,
|
Props,
|
||||||
@ -484,6 +476,9 @@ interface LegacyOptions<
|
|||||||
|
|
||||||
type MergedHook<T = (() => void)> = T | T[]
|
type MergedHook<T = (() => void)> = T | T[]
|
||||||
|
|
||||||
|
export type MergedComponentOptions = ComponentOptions &
|
||||||
|
MergedComponentOptionsOverride
|
||||||
|
|
||||||
export type MergedComponentOptionsOverride = {
|
export type MergedComponentOptionsOverride = {
|
||||||
beforeCreate?: MergedHook
|
beforeCreate?: MergedHook
|
||||||
created?: MergedHook
|
created?: MergedHook
|
||||||
@ -541,26 +536,23 @@ function createDuplicateChecker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataFn = (vm: ComponentPublicInstance) => any
|
|
||||||
|
|
||||||
export let shouldCacheAccess = true
|
export let shouldCacheAccess = true
|
||||||
|
|
||||||
export function applyOptions(
|
export function applyOptions(instance: ComponentInternalInstance) {
|
||||||
instance: ComponentInternalInstance,
|
const options = resolveMergedOptions(instance)
|
||||||
options: ComponentOptions,
|
const publicThis = instance.proxy!
|
||||||
deferredData: DataFn[] = [],
|
const ctx = instance.ctx
|
||||||
deferredWatch: ComponentWatchOptions[] = [],
|
|
||||||
deferredProvide: (Data | Function)[] = [],
|
// do not cache property access on public proxy during state initialization
|
||||||
asMixin: boolean = false
|
shouldCacheAccess = false
|
||||||
) {
|
|
||||||
if (__COMPAT__ && isFunction(options)) {
|
// call beforeCreate first before accessing other options since
|
||||||
options = options.options
|
// the hook may mutate resolved options (#2791)
|
||||||
|
if (options.beforeCreate) {
|
||||||
|
callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
// composition
|
|
||||||
mixins,
|
|
||||||
extends: extendsOptions,
|
|
||||||
// state
|
// state
|
||||||
data: dataOptions,
|
data: dataOptions,
|
||||||
computed: computedOptions,
|
computed: computedOptions,
|
||||||
@ -569,6 +561,7 @@ export function applyOptions(
|
|||||||
provide: provideOptions,
|
provide: provideOptions,
|
||||||
inject: injectOptions,
|
inject: injectOptions,
|
||||||
// lifecycle
|
// lifecycle
|
||||||
|
created,
|
||||||
beforeMount,
|
beforeMount,
|
||||||
mounted,
|
mounted,
|
||||||
beforeUpdate,
|
beforeUpdate,
|
||||||
@ -586,50 +579,13 @@ export function applyOptions(
|
|||||||
serverPrefetch,
|
serverPrefetch,
|
||||||
// public API
|
// public API
|
||||||
expose,
|
expose,
|
||||||
inheritAttrs
|
inheritAttrs,
|
||||||
|
// assets
|
||||||
|
components,
|
||||||
|
directives,
|
||||||
|
filters
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const publicThis = instance.proxy!
|
|
||||||
const ctx = instance.ctx
|
|
||||||
const globalMixins = instance.appContext.mixins
|
|
||||||
|
|
||||||
// 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
|
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -681,33 +637,45 @@ export function applyOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!asMixin) {
|
if (dataOptions) {
|
||||||
if (deferredData.length) {
|
if (__DEV__ && !isFunction(dataOptions)) {
|
||||||
deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis))
|
warn(
|
||||||
|
`The data option must be a function. ` +
|
||||||
|
`Plain object usage is no longer supported.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (dataOptions) {
|
const data = (dataOptions as any).call(publicThis, publicThis)
|
||||||
// @ts-ignore dataOptions is not fully type safe
|
if (__DEV__ && isPromise(data)) {
|
||||||
resolveData(instance, dataOptions, publicThis)
|
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 (__DEV__) {
|
if (!isObject(data)) {
|
||||||
const rawData = toRaw(instance.data)
|
__DEV__ && warn(`data() should return an object.`)
|
||||||
for (const key in rawData) {
|
} else {
|
||||||
checkDuplicateProperties!(OptionTypes.DATA, key)
|
instance.data = reactive(data)
|
||||||
// expose data on ctx during dev
|
if (__DEV__) {
|
||||||
if (key[0] !== '$' && key[0] !== '_') {
|
for (const key in data) {
|
||||||
Object.defineProperty(ctx, key, {
|
checkDuplicateProperties!(OptionTypes.DATA, key)
|
||||||
configurable: true,
|
// expose data on ctx during dev
|
||||||
enumerable: true,
|
if (key[0] !== '$' && key[0] !== '_') {
|
||||||
get: () => rawData[key],
|
Object.defineProperty(ctx, key, {
|
||||||
set: NOOP
|
configurable: true,
|
||||||
})
|
enumerable: true,
|
||||||
|
get: () => data[key],
|
||||||
|
set: NOOP
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (dataOptions) {
|
|
||||||
deferredData.push(dataOptions as DataFn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// state initialization complete at this point - start caching access
|
||||||
|
shouldCacheAccess = true
|
||||||
|
|
||||||
if (computedOptions) {
|
if (computedOptions) {
|
||||||
for (const key in computedOptions) {
|
for (const key in computedOptions) {
|
||||||
const opt = (computedOptions as ComputedOptions)[key]
|
const opt = (computedOptions as ComputedOptions)[key]
|
||||||
@ -746,47 +714,29 @@ export function applyOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (watchOptions) {
|
if (watchOptions) {
|
||||||
deferredWatch.push(watchOptions)
|
for (const key in watchOptions) {
|
||||||
}
|
createWatcher(watchOptions[key], ctx, publicThis, key)
|
||||||
if (!asMixin && deferredWatch.length) {
|
}
|
||||||
deferredWatch.forEach(watchOptions => {
|
|
||||||
for (const key in watchOptions) {
|
|
||||||
createWatcher(watchOptions[key], ctx, publicThis, key)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provideOptions) {
|
if (provideOptions) {
|
||||||
deferredProvide.push(provideOptions)
|
const provides = isFunction(provideOptions)
|
||||||
}
|
? provideOptions.call(publicThis)
|
||||||
if (!asMixin && deferredProvide.length) {
|
: provideOptions
|
||||||
deferredProvide.forEach(provideOptions => {
|
Reflect.ownKeys(provides).forEach(key => {
|
||||||
const provides = isFunction(provideOptions)
|
provide(key, provides[key])
|
||||||
? provideOptions.call(publicThis)
|
|
||||||
: provideOptions
|
|
||||||
Reflect.ownKeys(provides).forEach(key => {
|
|
||||||
provide(key, provides[key])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifecycle options
|
if (created) {
|
||||||
if (!asMixin) {
|
callHook(created, instance, LifecycleHooks.CREATED)
|
||||||
callSyncHook(
|
|
||||||
'created',
|
|
||||||
LifecycleHooks.CREATED,
|
|
||||||
options,
|
|
||||||
instance,
|
|
||||||
globalMixins
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerLifecycleHook(
|
function registerLifecycleHook(
|
||||||
register: Function,
|
register: Function,
|
||||||
hook?: Function | Function[]
|
hook?: Function | Function[]
|
||||||
) {
|
) {
|
||||||
// Array lifecycle hooks are only present in the compat build
|
if (isArray(hook)) {
|
||||||
if (__COMPAT__ && isArray(hook)) {
|
|
||||||
hook.forEach(_hook => register(_hook.bind(publicThis)))
|
hook.forEach(_hook => register(_hook.bind(publicThis)))
|
||||||
} else if (hook) {
|
} else if (hook) {
|
||||||
register((hook as Function).bind(publicThis))
|
register((hook as Function).bind(publicThis))
|
||||||
@ -822,56 +772,34 @@ export function applyOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(expose)) {
|
if (isArray(expose)) {
|
||||||
if (!asMixin) {
|
if (expose.length) {
|
||||||
if (expose.length) {
|
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
|
||||||
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
|
expose.forEach(key => {
|
||||||
expose.forEach(key => {
|
exposed[key] = toRef(publicThis, key as any)
|
||||||
exposed[key] = toRef(publicThis, key as any)
|
})
|
||||||
})
|
} else if (!instance.exposed) {
|
||||||
} else if (!instance.exposed) {
|
instance.exposed = EMPTY_OBJ
|
||||||
instance.exposed = EMPTY_OBJ
|
|
||||||
}
|
|
||||||
} else if (__DEV__) {
|
|
||||||
warn(`The \`expose\` option is ignored when used in mixins.`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// options that are handled when creating the instance but also need to be
|
// options that are handled when creating the instance but also need to be
|
||||||
// applied from mixins
|
// applied from mixins
|
||||||
if (asMixin) {
|
if (render && instance.render === NOOP) {
|
||||||
if (render && instance.render === NOOP) {
|
instance.render = render as InternalRenderFunction
|
||||||
instance.render = render as InternalRenderFunction
|
}
|
||||||
}
|
if (inheritAttrs != null) {
|
||||||
|
instance.inheritAttrs = inheritAttrs
|
||||||
if (inheritAttrs != null && instance.type.inheritAttrs == null) {
|
|
||||||
instance.inheritAttrs = inheritAttrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// asset options.
|
|
||||||
// To reduce memory usage, only components with mixins or extends will have
|
|
||||||
// resolved asset registry attached to instance.
|
|
||||||
resolveInstanceAssets(instance, options, COMPONENTS)
|
|
||||||
resolveInstanceAssets(instance, options, DIRECTIVES)
|
|
||||||
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.FILTERS, instance)) {
|
|
||||||
resolveInstanceAssets(instance, options, FILTERS)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function resolveInstanceAssets(
|
// asset options.
|
||||||
instance: ComponentInternalInstance,
|
if (components) instance.components = components as any
|
||||||
mixin: ComponentOptions,
|
if (directives) instance.directives = directives
|
||||||
type: AssetTypes
|
if (
|
||||||
) {
|
__COMPAT__ &&
|
||||||
if (mixin[type]) {
|
filters &&
|
||||||
extend(
|
isCompatEnabled(DeprecationTypes.FILTERS, instance)
|
||||||
instance[type] ||
|
) {
|
||||||
(instance[type] = extend(
|
instance.filters = filters
|
||||||
{},
|
|
||||||
(instance.type as ComponentOptions)[type]
|
|
||||||
) as any),
|
|
||||||
mixin[type]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,129 +809,43 @@ export function resolveInjections(
|
|||||||
checkDuplicateProperties = NOOP as any
|
checkDuplicateProperties = NOOP as any
|
||||||
) {
|
) {
|
||||||
if (isArray(injectOptions)) {
|
if (isArray(injectOptions)) {
|
||||||
for (let i = 0; i < injectOptions.length; i++) {
|
injectOptions = normalizeInject(injectOptions)!
|
||||||
const key = injectOptions[i]
|
}
|
||||||
ctx[key] = inject(key)
|
for (const key in injectOptions) {
|
||||||
if (__DEV__) {
|
const opt = (injectOptions as ObjectInjectOptions)[key]
|
||||||
checkDuplicateProperties!(OptionTypes.INJECT, key)
|
if (isObject(opt)) {
|
||||||
}
|
if ('default' in opt) {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const key in injectOptions) {
|
|
||||||
const opt = injectOptions[key]
|
|
||||||
if (isObject(opt)) {
|
|
||||||
ctx[key] = inject(
|
ctx[key] = inject(
|
||||||
opt.from || key,
|
opt.from || key,
|
||||||
opt.default,
|
opt.default,
|
||||||
true /* treat default function as factory */
|
true /* treat default function as factory */
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ctx[key] = inject(opt)
|
ctx[key] = inject(opt.from || key)
|
||||||
}
|
}
|
||||||
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() + <Suspense>.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
extend(instance.data, data)
|
ctx[key] = inject(opt)
|
||||||
|
}
|
||||||
|
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(
|
export function createWatcher(
|
||||||
raw: ComponentWatchOptionItem,
|
raw: ComponentWatchOptionItem,
|
||||||
ctx: Data,
|
ctx: Data,
|
||||||
@ -1047,7 +889,7 @@ export function createWatcher(
|
|||||||
*/
|
*/
|
||||||
export function resolveMergedOptions(
|
export function resolveMergedOptions(
|
||||||
instance: ComponentInternalInstance
|
instance: ComponentInternalInstance
|
||||||
): ComponentOptions & MergedComponentOptionsOverride {
|
): MergedComponentOptions {
|
||||||
const base = instance.type as ComponentOptions
|
const base = instance.type as ComponentOptions
|
||||||
const { mixins, extends: extendsOptions } = base
|
const { mixins, extends: extendsOptions } = base
|
||||||
const {
|
const {
|
||||||
@ -1057,7 +899,7 @@ export function resolveMergedOptions(
|
|||||||
} = instance.appContext
|
} = instance.appContext
|
||||||
const cached = cache.get(base)
|
const cached = cache.get(base)
|
||||||
|
|
||||||
let resolved: ComponentOptions
|
let resolved: MergedComponentOptions
|
||||||
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
resolved = cached
|
resolved = cached
|
||||||
@ -1066,17 +908,17 @@ export function resolveMergedOptions(
|
|||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
|
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
|
||||||
) {
|
) {
|
||||||
resolved = extend({}, base)
|
resolved = extend({}, base) as MergedComponentOptions
|
||||||
resolved.parent = instance.parent && instance.parent.proxy
|
resolved.parent = instance.parent && instance.parent.proxy
|
||||||
resolved.propsData = instance.vnode.props
|
resolved.propsData = instance.vnode.props
|
||||||
} else {
|
} else {
|
||||||
resolved = base
|
resolved = base as MergedComponentOptions
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolved = {}
|
resolved = {}
|
||||||
if (globalMixins.length) {
|
if (globalMixins.length) {
|
||||||
globalMixins.forEach(m =>
|
globalMixins.forEach(m =>
|
||||||
mergeOptions(resolved, m, optionMergeStrategies)
|
mergeOptions(resolved, m, optionMergeStrategies, true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
mergeOptions(resolved, base, optionMergeStrategies)
|
mergeOptions(resolved, base, optionMergeStrategies)
|
||||||
@ -1089,7 +931,8 @@ export function resolveMergedOptions(
|
|||||||
export function mergeOptions(
|
export function mergeOptions(
|
||||||
to: any,
|
to: any,
|
||||||
from: any,
|
from: any,
|
||||||
strats: Record<string, OptionMergeFunction>
|
strats: Record<string, OptionMergeFunction>,
|
||||||
|
asMixin = false
|
||||||
) {
|
) {
|
||||||
if (__COMPAT__ && isFunction(from)) {
|
if (__COMPAT__ && isFunction(from)) {
|
||||||
from = from.options
|
from = from.options
|
||||||
@ -1098,18 +941,110 @@ export function mergeOptions(
|
|||||||
const { mixins, extends: extendsOptions } = from
|
const { mixins, extends: extendsOptions } = from
|
||||||
|
|
||||||
if (extendsOptions) {
|
if (extendsOptions) {
|
||||||
mergeOptions(to, extendsOptions, strats)
|
mergeOptions(to, extendsOptions, strats, true)
|
||||||
}
|
}
|
||||||
if (mixins) {
|
if (mixins) {
|
||||||
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, strats))
|
mixins.forEach((m: ComponentOptionsMixin) =>
|
||||||
|
mergeOptions(to, m, strats, true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
if (strats && hasOwn(strats, key)) {
|
if (asMixin && key === 'expose') {
|
||||||
to[key] = strats[key](to[key], from[key])
|
__DEV__ &&
|
||||||
|
warn(
|
||||||
|
`"expose" option is ignored when declared in mixins or extends. ` +
|
||||||
|
`It should only be declared in the base component itself.`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
to[key] = from[key]
|
const strat = internalOptionMergeStrats[key] || (strats && strats[key])
|
||||||
|
to[key] = strat ? strat(to[key], from[key]) : from[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const internalOptionMergeStrats: Record<string, Function> = {
|
||||||
|
data: mergeDataFn,
|
||||||
|
props: mergeObjectOptions, // TODO
|
||||||
|
emits: mergeObjectOptions, // TODO
|
||||||
|
// objects
|
||||||
|
methods: mergeObjectOptions,
|
||||||
|
computed: mergeObjectOptions,
|
||||||
|
// lifecycle
|
||||||
|
beforeCreate: mergeHook,
|
||||||
|
created: mergeHook,
|
||||||
|
beforeMount: mergeHook,
|
||||||
|
mounted: mergeHook,
|
||||||
|
beforeUpdate: mergeHook,
|
||||||
|
updated: mergeHook,
|
||||||
|
beforeDestroy: mergeHook,
|
||||||
|
destroyed: mergeHook,
|
||||||
|
activated: mergeHook,
|
||||||
|
deactivated: mergeHook,
|
||||||
|
errorCaptured: mergeHook,
|
||||||
|
serverPrefetch: mergeHook,
|
||||||
|
// assets
|
||||||
|
components: mergeObjectOptions,
|
||||||
|
directives: mergeObjectOptions,
|
||||||
|
// watch has special merge behavior in v2, but isn't actually needed in v3.
|
||||||
|
// since we are only exposing these for compat and nobody should be relying
|
||||||
|
// on the watch-specific behavior, just expose the object merge strat.
|
||||||
|
watch: mergeObjectOptions,
|
||||||
|
// 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 mergeHook(
|
||||||
|
to: Function[] | Function | undefined,
|
||||||
|
from: Function | Function[]
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -99,7 +99,7 @@ function resolveAsset(
|
|||||||
|
|
||||||
const res =
|
const res =
|
||||||
// local registration
|
// local registration
|
||||||
// check instance[type] first for components with mixin or extends.
|
// check instance[type] first which is resolved for options API
|
||||||
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
|
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
|
||||||
// global registration
|
// global registration
|
||||||
resolve(instance.appContext[type], name)
|
resolve(instance.appContext[type], name)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user