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
|
||||
}
|
||||
|
||||
export type OptionMergeFunction = (
|
||||
to: unknown,
|
||||
from: unknown,
|
||||
instance: any,
|
||||
key: string
|
||||
) => any
|
||||
export type OptionMergeFunction = (to: unknown, from: unknown) => any
|
||||
|
||||
export interface AppConfig {
|
||||
// @private
|
||||
@ -97,6 +92,13 @@ export interface AppContext {
|
||||
components: Record<string, Component>
|
||||
directives: Record<string, Directive>
|
||||
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
|
||||
* @internal
|
||||
@ -137,7 +139,8 @@ export function createAppContext(): AppContext {
|
||||
mixins: [],
|
||||
components: {},
|
||||
directives: {},
|
||||
provides: Object.create(null)
|
||||
provides: Object.create(null),
|
||||
cache: new WeakMap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,6 @@ import {
|
||||
legacyresolveScopedSlots
|
||||
} from './renderHelpers'
|
||||
import { resolveFilter } from '../helpers/resolveAssets'
|
||||
import { resolveMergedOptions } from '../componentOptions'
|
||||
import { InternalSlots, Slots } from '../componentSlots'
|
||||
import { ContextualRenderFn } from '../componentRenderContext'
|
||||
|
||||
@ -128,16 +127,6 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||
// needed by many libs / render fns
|
||||
$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...
|
||||
_self: i => i.proxy,
|
||||
_uid: i => i.uid,
|
||||
|
@ -79,6 +79,7 @@ import {
|
||||
DIRECTIVES,
|
||||
FILTERS
|
||||
} from './helpers/resolveAssets'
|
||||
import { OptionMergeFunction } from './apiCreateApp'
|
||||
|
||||
/**
|
||||
* Interface for declaring custom options.
|
||||
@ -194,11 +195,6 @@ export interface ComponentOptionsBase<
|
||||
* @internal
|
||||
*/
|
||||
__asyncResolved?: ConcreteComponent
|
||||
/**
|
||||
* cache for merged $options
|
||||
* @internal
|
||||
*/
|
||||
__merged?: ComponentOptions
|
||||
|
||||
// Type differentiators ------------------------------------------------------
|
||||
|
||||
@ -486,6 +482,28 @@ interface LegacyOptions<
|
||||
__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 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(
|
||||
instance: ComponentInternalInstance
|
||||
): ComponentOptions {
|
||||
const raw = instance.type as ComponentOptions
|
||||
const { __merged, mixins, extends: extendsOptions } = raw
|
||||
if (__merged) return __merged
|
||||
const globalMixins = instance.appContext.mixins
|
||||
if (!globalMixins.length && !mixins && !extendsOptions) return raw
|
||||
const options = {}
|
||||
globalMixins.forEach(m => mergeOptions(options, m, instance))
|
||||
mergeOptions(options, raw, instance)
|
||||
return (raw.__merged = options)
|
||||
): ComponentOptions & MergedComponentOptionsOverride {
|
||||
const base = instance.type as ComponentOptions
|
||||
const { mixins, extends: extendsOptions } = base
|
||||
const {
|
||||
mixins: globalMixins,
|
||||
cache,
|
||||
config: { optionMergeStrategies }
|
||||
} = instance.appContext
|
||||
const cached = cache.get(base)
|
||||
|
||||
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(
|
||||
to: any,
|
||||
from: any,
|
||||
instance?: ComponentInternalInstance | null,
|
||||
strats = instance && instance.appContext.config.optionMergeStrategies
|
||||
strats: Record<string, OptionMergeFunction>
|
||||
) {
|
||||
if (__COMPAT__ && isFunction(from)) {
|
||||
from = from.options
|
||||
@ -1048,15 +1097,16 @@ export function mergeOptions(
|
||||
|
||||
const { mixins, extends: extendsOptions } = from
|
||||
|
||||
extendsOptions && mergeOptions(to, extendsOptions, instance, strats)
|
||||
mixins &&
|
||||
mixins.forEach((m: ComponentOptionsMixin) =>
|
||||
mergeOptions(to, m, instance, strats)
|
||||
)
|
||||
if (extendsOptions) {
|
||||
mergeOptions(to, extendsOptions, strats)
|
||||
}
|
||||
if (mixins) {
|
||||
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, strats))
|
||||
}
|
||||
|
||||
for (const key in from) {
|
||||
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 {
|
||||
to[key] = from[key]
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ import {
|
||||
OptionTypesType,
|
||||
OptionTypesKeys,
|
||||
resolveMergedOptions,
|
||||
shouldCacheAccess
|
||||
shouldCacheAccess,
|
||||
MergedComponentOptionsOverride
|
||||
} from './componentOptions'
|
||||
import { EmitsOptions, EmitFn } from './componentEmits'
|
||||
import { Slots } from './componentSlots'
|
||||
@ -188,7 +189,7 @@ export type ComponentPublicInstance<
|
||||
$parent: ComponentPublicInstance | null
|
||||
$emit: EmitFn<E>
|
||||
$el: any
|
||||
$options: Options
|
||||
$options: Options & MergedComponentOptionsOverride
|
||||
$forceUpdate: ReactiveEffect
|
||||
$nextTick: typeof nextTick
|
||||
$watch(
|
||||
|
Loading…
Reference in New Issue
Block a user