feat(runtime-core): support config.optionMergeStrategies
Note the behavior is different from Vue 2: - merge strategies no longer apply to built-in options. - the default value is now an empty object and no longer exposes merge strategies for built-in options.
This commit is contained in:
parent
123738727a
commit
528621ba41
@ -8,7 +8,8 @@ import {
|
|||||||
nextTick,
|
nextTick,
|
||||||
renderToString,
|
renderToString,
|
||||||
ref,
|
ref,
|
||||||
defineComponent
|
defineComponent,
|
||||||
|
createApp
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { mockWarn } from '@vue/shared'
|
import { mockWarn } from '@vue/shared'
|
||||||
|
|
||||||
@ -562,6 +563,28 @@ describe('api: options', () => {
|
|||||||
expect(serializeInner(root)).toBe(`<div>1,1,3</div>`)
|
expect(serializeInner(root)).toBe(`<div>1,1,3</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('optionMergeStrategies', () => {
|
||||||
|
let merged: string
|
||||||
|
const App = defineComponent({
|
||||||
|
render() {},
|
||||||
|
mixins: [{ foo: 'mixin' }],
|
||||||
|
extends: { foo: 'extends' },
|
||||||
|
foo: 'local',
|
||||||
|
mounted() {
|
||||||
|
merged = this.$options.foo
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mixin({
|
||||||
|
foo: 'global'
|
||||||
|
})
|
||||||
|
app.config.optionMergeStrategies.foo = (a, b) => (a ? `${a},` : ``) + b
|
||||||
|
|
||||||
|
app.mount(nodeOps.createElement('div'))
|
||||||
|
expect(merged!).toBe('global,extends,mixin,local')
|
||||||
|
})
|
||||||
|
|
||||||
describe('warnings', () => {
|
describe('warnings', () => {
|
||||||
mockWarn()
|
mockWarn()
|
||||||
|
|
||||||
|
@ -36,11 +36,21 @@ export interface App<HostElement = any> {
|
|||||||
_context: AppContext
|
_context: AppContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OptionMergeFunction = (
|
||||||
|
to: unknown,
|
||||||
|
from: unknown,
|
||||||
|
instance: any,
|
||||||
|
key: string
|
||||||
|
) => any
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
|
// @private
|
||||||
|
readonly isNativeTag?: (tag: string) => boolean
|
||||||
|
|
||||||
devtools: boolean
|
devtools: boolean
|
||||||
performance: boolean
|
performance: boolean
|
||||||
readonly isNativeTag?: (tag: string) => boolean
|
optionMergeStrategies: Record<string, OptionMergeFunction>
|
||||||
isCustomElement?: (tag: string) => boolean
|
isCustomElement: (tag: string) => boolean
|
||||||
errorHandler?: (
|
errorHandler?: (
|
||||||
err: unknown,
|
err: unknown,
|
||||||
instance: ComponentPublicInstance | null,
|
instance: ComponentPublicInstance | null,
|
||||||
@ -73,9 +83,10 @@ export type Plugin =
|
|||||||
export function createAppContext(): AppContext {
|
export function createAppContext(): AppContext {
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {
|
||||||
|
isNativeTag: NO,
|
||||||
devtools: true,
|
devtools: true,
|
||||||
performance: false,
|
performance: false,
|
||||||
isNativeTag: NO,
|
optionMergeStrategies: {},
|
||||||
isCustomElement: NO,
|
isCustomElement: NO,
|
||||||
errorHandler: undefined,
|
errorHandler: undefined,
|
||||||
warnHandler: undefined
|
warnHandler: undefined
|
||||||
|
@ -14,7 +14,8 @@ import {
|
|||||||
isObject,
|
isObject,
|
||||||
isArray,
|
isArray,
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
NOOP
|
NOOP,
|
||||||
|
hasOwn
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { computed } from './apiComputed'
|
import { computed } from './apiComputed'
|
||||||
import { watch, WatchOptions, WatchCallback } from './apiWatch'
|
import { watch, WatchOptions, WatchCallback } from './apiWatch'
|
||||||
@ -75,11 +76,16 @@ export interface ComponentOptionsBase<
|
|||||||
directives?: Record<string, Directive>
|
directives?: Record<string, Directive>
|
||||||
inheritAttrs?: boolean
|
inheritAttrs?: boolean
|
||||||
|
|
||||||
|
// Internal ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// marker for AsyncComponentWrapper
|
||||||
|
__asyncLoader?: () => Promise<Component>
|
||||||
|
// cache for merged $options
|
||||||
|
__merged?: ComponentOptions
|
||||||
|
|
||||||
// type-only differentiator to separate OptionWithoutProps from a constructor
|
// type-only differentiator to separate OptionWithoutProps from a constructor
|
||||||
// type returned by defineComponent() or FunctionalComponent
|
// type returned by defineComponent() or FunctionalComponent
|
||||||
call?: never
|
call?: never
|
||||||
// marker for AsyncComponentWrapper
|
|
||||||
__asyncLoader?: () => Promise<Component>
|
|
||||||
// type-only differentiators for built-in Vnode types
|
// type-only differentiators for built-in Vnode types
|
||||||
__isFragment?: never
|
__isFragment?: never
|
||||||
__isPortal?: never
|
__isPortal?: never
|
||||||
@ -161,7 +167,8 @@ export interface LegacyOptions<
|
|||||||
C extends ComputedOptions,
|
C extends ComputedOptions,
|
||||||
M extends MethodOptions
|
M extends MethodOptions
|
||||||
> {
|
> {
|
||||||
el?: any
|
// allow any custom options
|
||||||
|
[key: string]: any
|
||||||
|
|
||||||
// state
|
// state
|
||||||
// Limitation: we cannot expose RawBindings on the `this` context for data
|
// Limitation: we cannot expose RawBindings on the `this` context for data
|
||||||
@ -501,3 +508,31 @@ function createWatcher(
|
|||||||
warn(`Invalid watch option: "${key}"`)
|
warn(`Invalid watch option: "${key}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 && !mixins && !extendsOptions) return raw
|
||||||
|
const options = {}
|
||||||
|
globalMixins && globalMixins.forEach(m => mergeOptions(options, m, instance))
|
||||||
|
extendsOptions && mergeOptions(options, extendsOptions, instance)
|
||||||
|
mixins && mixins.forEach(m => mergeOptions(options, m, instance))
|
||||||
|
mergeOptions(options, raw, instance)
|
||||||
|
return (raw.__merged = options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
|
||||||
|
const strats = instance.appContext.config.optionMergeStrategies
|
||||||
|
for (const key in from) {
|
||||||
|
const strat = strats && strats[key]
|
||||||
|
if (strat) {
|
||||||
|
to[key] = strat(to[key], from[key], instance.proxy, key)
|
||||||
|
} else if (!hasOwn(to, key)) {
|
||||||
|
to[key] = from[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,8 @@ import {
|
|||||||
ExtractComputedReturns,
|
ExtractComputedReturns,
|
||||||
ComponentOptionsBase,
|
ComponentOptionsBase,
|
||||||
ComputedOptions,
|
ComputedOptions,
|
||||||
MethodOptions
|
MethodOptions,
|
||||||
|
resolveMergedOptions
|
||||||
} from './apiOptions'
|
} from './apiOptions'
|
||||||
import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
|
import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
@ -61,7 +62,7 @@ const publicPropertiesMap: Record<
|
|||||||
$parent: i => i.parent,
|
$parent: i => i.parent,
|
||||||
$root: i => i.root,
|
$root: i => i.root,
|
||||||
$emit: i => i.emit,
|
$emit: i => i.emit,
|
||||||
$options: i => i.type,
|
$options: i => (__FEATURE_OPTIONS__ ? resolveMergedOptions(i) : i.type),
|
||||||
$forceUpdate: i => () => queueJob(i.update),
|
$forceUpdate: i => () => queueJob(i.update),
|
||||||
$nextTick: () => nextTick,
|
$nextTick: () => nextTick,
|
||||||
$watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP
|
$watch: __FEATURE_OPTIONS__ ? i => instanceWatch.bind(i) : NOOP
|
||||||
|
@ -163,7 +163,8 @@ export {
|
|||||||
AppConfig,
|
AppConfig,
|
||||||
AppContext,
|
AppContext,
|
||||||
Plugin,
|
Plugin,
|
||||||
CreateAppFunction
|
CreateAppFunction,
|
||||||
|
OptionMergeFunction
|
||||||
} from './apiCreateApp'
|
} from './apiCreateApp'
|
||||||
export {
|
export {
|
||||||
VNode,
|
VNode,
|
||||||
|
Loading…
Reference in New Issue
Block a user