refactor: adjust component options merge cache strategy
BREAKING CHANGE: optionMergeStrategies functions no longer receive the component instance as the 3rd argument. The argument was technically internal in Vue 2 and only used for generating warnings, and should not be needed in userland code. This removal enables much more efficient caching of option merging.
This commit is contained in:
parent
44996d1a0a
commit
1e35a860b9
@ -53,12 +53,7 @@ export interface App<HostElement = any> {
|
|||||||
_createRoot?(options: ComponentOptions): ComponentPublicInstance
|
_createRoot?(options: ComponentOptions): ComponentPublicInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OptionMergeFunction = (
|
export type OptionMergeFunction = (to: unknown, from: unknown) => any
|
||||||
to: unknown,
|
|
||||||
from: unknown,
|
|
||||||
instance: any,
|
|
||||||
key: string
|
|
||||||
) => any
|
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
// @private
|
// @private
|
||||||
@ -97,6 +92,13 @@ export interface AppContext {
|
|||||||
components: Record<string, Component>
|
components: Record<string, Component>
|
||||||
directives: Record<string, Directive>
|
directives: Record<string, Directive>
|
||||||
provides: Record<string | symbol, any>
|
provides: Record<string | symbol, any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for merged/normalized component options
|
||||||
|
* Each app instance has its own cache because app-level global mixins and
|
||||||
|
* optionMergeStrategies can affect merge behavior.
|
||||||
|
*/
|
||||||
|
cache: WeakMap<ComponentOptions, ComponentOptions>
|
||||||
/**
|
/**
|
||||||
* Flag for de-optimizing props normalization
|
* Flag for de-optimizing props normalization
|
||||||
* @internal
|
* @internal
|
||||||
@ -137,7 +139,8 @@ export function createAppContext(): AppContext {
|
|||||||
mixins: [],
|
mixins: [],
|
||||||
components: {},
|
components: {},
|
||||||
directives: {},
|
directives: {},
|
||||||
provides: Object.create(null)
|
provides: Object.create(null),
|
||||||
|
cache: new WeakMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import {
|
|||||||
legacyresolveScopedSlots
|
legacyresolveScopedSlots
|
||||||
} from './renderHelpers'
|
} from './renderHelpers'
|
||||||
import { resolveFilter } from '../helpers/resolveAssets'
|
import { resolveFilter } from '../helpers/resolveAssets'
|
||||||
import { resolveMergedOptions } from '../componentOptions'
|
|
||||||
import { InternalSlots, Slots } from '../componentSlots'
|
import { InternalSlots, Slots } from '../componentSlots'
|
||||||
import { ContextualRenderFn } from '../componentRenderContext'
|
import { ContextualRenderFn } from '../componentRenderContext'
|
||||||
|
|
||||||
@ -128,16 +127,6 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
|||||||
// needed by many libs / render fns
|
// needed by many libs / render fns
|
||||||
$vnode: i => i.vnode,
|
$vnode: i => i.vnode,
|
||||||
|
|
||||||
// inject addtional properties into $options for compat
|
|
||||||
// e.g. vuex needs this.$options.parent
|
|
||||||
$options: i => {
|
|
||||||
let res = resolveMergedOptions(i)
|
|
||||||
if (res === i.type) res = i.type.__merged = extend({}, res)
|
|
||||||
res.parent = i.proxy!.$parent
|
|
||||||
res.propsData = i.vnode.props
|
|
||||||
return res
|
|
||||||
},
|
|
||||||
|
|
||||||
// some private properties that are likely accessed...
|
// some private properties that are likely accessed...
|
||||||
_self: i => i.proxy,
|
_self: i => i.proxy,
|
||||||
_uid: i => i.uid,
|
_uid: i => i.uid,
|
||||||
|
@ -79,6 +79,7 @@ import {
|
|||||||
DIRECTIVES,
|
DIRECTIVES,
|
||||||
FILTERS
|
FILTERS
|
||||||
} from './helpers/resolveAssets'
|
} from './helpers/resolveAssets'
|
||||||
|
import { OptionMergeFunction } from './apiCreateApp'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for declaring custom options.
|
* Interface for declaring custom options.
|
||||||
@ -194,11 +195,6 @@ export interface ComponentOptionsBase<
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
__asyncResolved?: ConcreteComponent
|
__asyncResolved?: ConcreteComponent
|
||||||
/**
|
|
||||||
* cache for merged $options
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
__merged?: ComponentOptions
|
|
||||||
|
|
||||||
// Type differentiators ------------------------------------------------------
|
// Type differentiators ------------------------------------------------------
|
||||||
|
|
||||||
@ -486,6 +482,28 @@ interface LegacyOptions<
|
|||||||
__differentiator?: keyof D | keyof C | keyof M
|
__differentiator?: keyof D | keyof C | keyof M
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MergedHook<T = (() => void)> = T | T[]
|
||||||
|
|
||||||
|
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 OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults'
|
||||||
|
|
||||||
export type OptionTypesType<
|
export type OptionTypesType<
|
||||||
@ -1022,25 +1040,56 @@ export function createWatcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
export function resolveMergedOptions(
|
||||||
instance: ComponentInternalInstance
|
instance: ComponentInternalInstance
|
||||||
): ComponentOptions {
|
): ComponentOptions & MergedComponentOptionsOverride {
|
||||||
const raw = instance.type as ComponentOptions
|
const base = instance.type as ComponentOptions
|
||||||
const { __merged, mixins, extends: extendsOptions } = raw
|
const { mixins, extends: extendsOptions } = base
|
||||||
if (__merged) return __merged
|
const {
|
||||||
const globalMixins = instance.appContext.mixins
|
mixins: globalMixins,
|
||||||
if (!globalMixins.length && !mixins && !extendsOptions) return raw
|
cache,
|
||||||
const options = {}
|
config: { optionMergeStrategies }
|
||||||
globalMixins.forEach(m => mergeOptions(options, m, instance))
|
} = instance.appContext
|
||||||
mergeOptions(options, raw, instance)
|
const cached = cache.get(base)
|
||||||
return (raw.__merged = options)
|
|
||||||
|
let resolved: ComponentOptions
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
resolved = cached
|
||||||
|
} else if (!globalMixins.length && !mixins && !extendsOptions) {
|
||||||
|
if (
|
||||||
|
__COMPAT__ &&
|
||||||
|
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
|
||||||
|
) {
|
||||||
|
resolved = extend({}, base)
|
||||||
|
resolved.parent = instance.parent && instance.parent.proxy
|
||||||
|
resolved.propsData = instance.vnode.props
|
||||||
|
} else {
|
||||||
|
resolved = base
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolved = {}
|
||||||
|
if (globalMixins.length) {
|
||||||
|
globalMixins.forEach(m =>
|
||||||
|
mergeOptions(resolved, m, optionMergeStrategies)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mergeOptions(resolved, base, optionMergeStrategies)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set(base, resolved)
|
||||||
|
return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeOptions(
|
export function mergeOptions(
|
||||||
to: any,
|
to: any,
|
||||||
from: any,
|
from: any,
|
||||||
instance?: ComponentInternalInstance | null,
|
strats: Record<string, OptionMergeFunction>
|
||||||
strats = instance && instance.appContext.config.optionMergeStrategies
|
|
||||||
) {
|
) {
|
||||||
if (__COMPAT__ && isFunction(from)) {
|
if (__COMPAT__ && isFunction(from)) {
|
||||||
from = from.options
|
from = from.options
|
||||||
@ -1048,15 +1097,16 @@ export function mergeOptions(
|
|||||||
|
|
||||||
const { mixins, extends: extendsOptions } = from
|
const { mixins, extends: extendsOptions } = from
|
||||||
|
|
||||||
extendsOptions && mergeOptions(to, extendsOptions, instance, strats)
|
if (extendsOptions) {
|
||||||
mixins &&
|
mergeOptions(to, extendsOptions, strats)
|
||||||
mixins.forEach((m: ComponentOptionsMixin) =>
|
}
|
||||||
mergeOptions(to, m, instance, strats)
|
if (mixins) {
|
||||||
)
|
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, strats))
|
||||||
|
}
|
||||||
|
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
if (strats && hasOwn(strats, key)) {
|
if (strats && hasOwn(strats, key)) {
|
||||||
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
|
to[key] = strats[key](to[key], from[key])
|
||||||
} else {
|
} else {
|
||||||
to[key] = from[key]
|
to[key] = from[key]
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ import {
|
|||||||
OptionTypesType,
|
OptionTypesType,
|
||||||
OptionTypesKeys,
|
OptionTypesKeys,
|
||||||
resolveMergedOptions,
|
resolveMergedOptions,
|
||||||
shouldCacheAccess
|
shouldCacheAccess,
|
||||||
|
MergedComponentOptionsOverride
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
import { EmitsOptions, EmitFn } from './componentEmits'
|
import { EmitsOptions, EmitFn } from './componentEmits'
|
||||||
import { Slots } from './componentSlots'
|
import { Slots } from './componentSlots'
|
||||||
@ -188,7 +189,7 @@ export type ComponentPublicInstance<
|
|||||||
$parent: ComponentPublicInstance | null
|
$parent: ComponentPublicInstance | null
|
||||||
$emit: EmitFn<E>
|
$emit: EmitFn<E>
|
||||||
$el: any
|
$el: any
|
||||||
$options: Options
|
$options: Options & MergedComponentOptionsOverride
|
||||||
$forceUpdate: ReactiveEffect
|
$forceUpdate: ReactiveEffect
|
||||||
$nextTick: typeof nextTick
|
$nextTick: typeof nextTick
|
||||||
$watch(
|
$watch(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user