fix(runtime-core): fix props/emits resolving with global mixins
fix #1975
This commit is contained in:
parent
2bbeea9a51
commit
8ed0b342d4
@ -178,40 +178,13 @@ describe('component: emit', () => {
|
|||||||
expect(fn).toHaveBeenCalledTimes(1)
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('isEmitListener', () => {
|
test('isEmitListener', () => {
|
||||||
test('array option', () => {
|
const options = { click: null }
|
||||||
const def1 = { emits: ['click'] }
|
expect(isEmitListener(options, 'onClick')).toBe(true)
|
||||||
expect(isEmitListener(def1, 'onClick')).toBe(true)
|
expect(isEmitListener(options, 'onclick')).toBe(false)
|
||||||
expect(isEmitListener(def1, 'onclick')).toBe(false)
|
expect(isEmitListener(options, 'onBlick')).toBe(false)
|
||||||
expect(isEmitListener(def1, 'onBlick')).toBe(false)
|
// .once listeners
|
||||||
})
|
expect(isEmitListener(options, 'onClickOnce')).toBe(true)
|
||||||
|
expect(isEmitListener(options, 'onclickOnce')).toBe(false)
|
||||||
test('object option', () => {
|
|
||||||
const def2 = { emits: { click: null } }
|
|
||||||
expect(isEmitListener(def2, 'onClick')).toBe(true)
|
|
||||||
expect(isEmitListener(def2, 'onclick')).toBe(false)
|
|
||||||
expect(isEmitListener(def2, 'onBlick')).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('with mixins and extends', () => {
|
|
||||||
const mixin1 = { emits: ['foo'] }
|
|
||||||
const mixin2 = { emits: ['bar'] }
|
|
||||||
const extend = { emits: ['baz'] }
|
|
||||||
const def3 = {
|
|
||||||
mixins: [mixin1, mixin2],
|
|
||||||
extends: extend
|
|
||||||
}
|
|
||||||
expect(isEmitListener(def3, 'onFoo')).toBe(true)
|
|
||||||
expect(isEmitListener(def3, 'onBar')).toBe(true)
|
|
||||||
expect(isEmitListener(def3, 'onBaz')).toBe(true)
|
|
||||||
expect(isEmitListener(def3, 'onclick')).toBe(false)
|
|
||||||
expect(isEmitListener(def3, 'onBlick')).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('.once listeners', () => {
|
|
||||||
const def2 = { emits: { click: null } }
|
|
||||||
expect(isEmitListener(def2, 'onClickOnce')).toBe(true)
|
|
||||||
expect(isEmitListener(def2, 'onclickOnce')).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
FunctionalComponent,
|
FunctionalComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
ref,
|
ref,
|
||||||
serializeInner
|
serializeInner,
|
||||||
|
createApp
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { render as domRender, nextTick } from 'vue'
|
import { render as domRender, nextTick } from 'vue'
|
||||||
|
|
||||||
@ -309,4 +310,44 @@ describe('component props', () => {
|
|||||||
expect(setupProps).toMatchObject(props)
|
expect(setupProps).toMatchObject(props)
|
||||||
expect(renderProxy.$props).toMatchObject(props)
|
expect(renderProxy.$props).toMatchObject(props)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('merging props from global mixins', () => {
|
||||||
|
let setupProps: any
|
||||||
|
let renderProxy: any
|
||||||
|
|
||||||
|
const M1 = {
|
||||||
|
props: ['m1']
|
||||||
|
}
|
||||||
|
const M2 = {
|
||||||
|
props: { m2: null }
|
||||||
|
}
|
||||||
|
const Comp = {
|
||||||
|
props: ['self'],
|
||||||
|
setup(props: any) {
|
||||||
|
setupProps = props
|
||||||
|
},
|
||||||
|
render(this: any) {
|
||||||
|
renderProxy = this
|
||||||
|
return h('div', [this.self, this.m1, this.m2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
self: 'from self, ',
|
||||||
|
m1: 'from mixin 1, ',
|
||||||
|
m2: 'from mixin 2'
|
||||||
|
}
|
||||||
|
const app = createApp(Comp, props)
|
||||||
|
app.mixin(M1)
|
||||||
|
app.mixin(M2)
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
app.mount(root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toMatch(
|
||||||
|
`from self, from mixin 1, from mixin 2`
|
||||||
|
)
|
||||||
|
expect(setupProps).toMatchObject(props)
|
||||||
|
expect(renderProxy.$props).toMatchObject(props)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -33,6 +33,7 @@ export interface App<HostElement = any> {
|
|||||||
provide<T>(key: InjectionKey<T> | string, value: T): this
|
provide<T>(key: InjectionKey<T> | string, value: T): this
|
||||||
|
|
||||||
// internal, but we need to expose these for the server-renderer and devtools
|
// internal, but we need to expose these for the server-renderer and devtools
|
||||||
|
_uid: number
|
||||||
_component: ConcreteComponent
|
_component: ConcreteComponent
|
||||||
_props: Data | null
|
_props: Data | null
|
||||||
_container: HostElement | null
|
_container: HostElement | null
|
||||||
@ -108,6 +109,8 @@ export type CreateAppFunction<HostElement> = (
|
|||||||
rootProps?: Data | null
|
rootProps?: Data | null
|
||||||
) => App<HostElement>
|
) => App<HostElement>
|
||||||
|
|
||||||
|
let uid = 0
|
||||||
|
|
||||||
export function createAppAPI<HostElement>(
|
export function createAppAPI<HostElement>(
|
||||||
render: RootRenderFunction,
|
render: RootRenderFunction,
|
||||||
hydrate?: RootHydrateFunction
|
hydrate?: RootHydrateFunction
|
||||||
@ -124,6 +127,7 @@ export function createAppAPI<HostElement>(
|
|||||||
let isMounted = false
|
let isMounted = false
|
||||||
|
|
||||||
const app: App = (context.app = {
|
const app: App = (context.app = {
|
||||||
|
_uid: uid++,
|
||||||
_component: rootComponent as ConcreteComponent,
|
_component: rootComponent as ConcreteComponent,
|
||||||
_props: rootProps,
|
_props: rootProps,
|
||||||
_container: null,
|
_container: null,
|
||||||
|
@ -18,7 +18,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
ComponentPropsOptions,
|
ComponentPropsOptions,
|
||||||
NormalizedPropsOptions,
|
NormalizedPropsOptions,
|
||||||
initProps
|
initProps,
|
||||||
|
normalizePropsOptions
|
||||||
} from './componentProps'
|
} from './componentProps'
|
||||||
import { Slots, initSlots, InternalSlots } from './componentSlots'
|
import { Slots, initSlots, InternalSlots } from './componentSlots'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
@ -30,7 +31,8 @@ import {
|
|||||||
EmitsOptions,
|
EmitsOptions,
|
||||||
ObjectEmitsOptions,
|
ObjectEmitsOptions,
|
||||||
EmitFn,
|
EmitFn,
|
||||||
emit
|
emit,
|
||||||
|
normalizeEmitsOptions
|
||||||
} from './componentEmits'
|
} from './componentEmits'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
@ -72,11 +74,11 @@ export interface ComponentInternalOptions {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
__props?: NormalizedPropsOptions | []
|
__props?: Record<number, NormalizedPropsOptions>
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
__emits?: ObjectEmitsOptions
|
__emits?: Record<number, ObjectEmitsOptions | null>
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -231,6 +233,16 @@ export interface ComponentInternalInstance {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
directives: Record<string, Directive> | null
|
directives: Record<string, Directive> | null
|
||||||
|
/**
|
||||||
|
* reoslved props options
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
propsOptions: NormalizedPropsOptions
|
||||||
|
/**
|
||||||
|
* resolved emits options
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
emitsOptions: ObjectEmitsOptions | null
|
||||||
|
|
||||||
// the rest are only for stateful components ---------------------------------
|
// the rest are only for stateful components ---------------------------------
|
||||||
|
|
||||||
@ -254,14 +266,17 @@ export interface ComponentInternalInstance {
|
|||||||
*/
|
*/
|
||||||
ctx: Data
|
ctx: Data
|
||||||
|
|
||||||
// internal state
|
// state
|
||||||
data: Data
|
data: Data
|
||||||
props: Data
|
props: Data
|
||||||
attrs: Data
|
attrs: Data
|
||||||
slots: InternalSlots
|
slots: InternalSlots
|
||||||
refs: Data
|
refs: Data
|
||||||
emit: EmitFn
|
emit: EmitFn
|
||||||
// used for keeping track of .once event handlers on components
|
/**
|
||||||
|
* used for keeping track of .once event handlers on components
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
emitted: Record<string, boolean> | null
|
emitted: Record<string, boolean> | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -387,6 +402,14 @@ export function createComponentInstance(
|
|||||||
components: null,
|
components: null,
|
||||||
directives: null,
|
directives: null,
|
||||||
|
|
||||||
|
// resolved props and emits options
|
||||||
|
propsOptions: normalizePropsOptions(type, appContext),
|
||||||
|
emitsOptions: normalizeEmitsOptions(type, appContext),
|
||||||
|
|
||||||
|
// emit
|
||||||
|
emit: null as any, // to be set immediately
|
||||||
|
emitted: null,
|
||||||
|
|
||||||
// state
|
// state
|
||||||
ctx: EMPTY_OBJ,
|
ctx: EMPTY_OBJ,
|
||||||
data: EMPTY_OBJ,
|
data: EMPTY_OBJ,
|
||||||
@ -419,9 +442,7 @@ export function createComponentInstance(
|
|||||||
a: null,
|
a: null,
|
||||||
rtg: null,
|
rtg: null,
|
||||||
rtc: null,
|
rtc: null,
|
||||||
ec: null,
|
ec: null
|
||||||
emit: null as any, // to be set immediately
|
|
||||||
emitted: null
|
|
||||||
}
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
instance.ctx = createRenderContext(instance)
|
instance.ctx = createRenderContext(instance)
|
||||||
|
@ -8,12 +8,16 @@ import {
|
|||||||
isFunction,
|
isFunction,
|
||||||
extend
|
extend
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { ComponentInternalInstance, ConcreteComponent } from './component'
|
import {
|
||||||
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
|
ConcreteComponent
|
||||||
|
} from './component'
|
||||||
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
|
||||||
import { UnionToIntersection } from './helpers/typeUtils'
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
import { devtoolsComponentEmit } from './devtools'
|
import { devtoolsComponentEmit } from './devtools'
|
||||||
|
import { AppContext } from './apiCreateApp'
|
||||||
|
|
||||||
export type ObjectEmitsOptions = Record<
|
export type ObjectEmitsOptions = Record<
|
||||||
string,
|
string,
|
||||||
@ -44,10 +48,12 @@ export function emit(
|
|||||||
const props = instance.vnode.props || EMPTY_OBJ
|
const props = instance.vnode.props || EMPTY_OBJ
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const options = normalizeEmitsOptions(instance.type)
|
const {
|
||||||
if (options) {
|
emitsOptions,
|
||||||
if (!(event in options)) {
|
propsOptions: [propsOptions]
|
||||||
const propsOptions = normalizePropsOptions(instance.type)[0]
|
} = instance
|
||||||
|
if (emitsOptions) {
|
||||||
|
if (!(event in emitsOptions)) {
|
||||||
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
|
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
|
||||||
warn(
|
warn(
|
||||||
`Component emitted event "${event}" but it is neither declared in ` +
|
`Component emitted event "${event}" but it is neither declared in ` +
|
||||||
@ -55,7 +61,7 @@ export function emit(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const validator = options[event]
|
const validator = emitsOptions[event]
|
||||||
if (isFunction(validator)) {
|
if (isFunction(validator)) {
|
||||||
const isValid = validator(...args)
|
const isValid = validator(...args)
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@ -98,11 +104,16 @@ export function emit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeEmitsOptions(
|
export function normalizeEmitsOptions(
|
||||||
comp: ConcreteComponent
|
comp: ConcreteComponent,
|
||||||
): ObjectEmitsOptions | undefined {
|
appContext: AppContext,
|
||||||
if (hasOwn(comp, '__emits')) {
|
asMixin = false
|
||||||
return comp.__emits
|
): ObjectEmitsOptions | null {
|
||||||
|
const appId = appContext.app ? appContext.app._uid : -1
|
||||||
|
const cache = comp.__emits || (comp.__emits = {})
|
||||||
|
const cached = cache[appId]
|
||||||
|
if (cached !== undefined) {
|
||||||
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = comp.emits
|
const raw = comp.emits
|
||||||
@ -111,18 +122,23 @@ function normalizeEmitsOptions(
|
|||||||
// apply mixin/extends props
|
// apply mixin/extends props
|
||||||
let hasExtends = false
|
let hasExtends = false
|
||||||
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
||||||
if (comp.extends) {
|
const extendEmits = (raw: ComponentOptions) => {
|
||||||
hasExtends = true
|
hasExtends = true
|
||||||
extend(normalized, normalizeEmitsOptions(comp.extends))
|
extend(normalized, normalizeEmitsOptions(raw, appContext, true))
|
||||||
|
}
|
||||||
|
if (!asMixin && appContext.mixins.length) {
|
||||||
|
appContext.mixins.forEach(extendEmits)
|
||||||
|
}
|
||||||
|
if (comp.extends) {
|
||||||
|
extendEmits(comp.extends)
|
||||||
}
|
}
|
||||||
if (comp.mixins) {
|
if (comp.mixins) {
|
||||||
hasExtends = true
|
comp.mixins.forEach(extendEmits)
|
||||||
comp.mixins.forEach(m => extend(normalized, normalizeEmitsOptions(m)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!raw && !hasExtends) {
|
if (!raw && !hasExtends) {
|
||||||
return (comp.__emits = undefined)
|
return (cache[appId] = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(raw)) {
|
if (isArray(raw)) {
|
||||||
@ -130,20 +146,22 @@ function normalizeEmitsOptions(
|
|||||||
} else {
|
} else {
|
||||||
extend(normalized, raw)
|
extend(normalized, raw)
|
||||||
}
|
}
|
||||||
return (comp.__emits = normalized)
|
return (cache[appId] = normalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if an incoming prop key is a declared emit event listener.
|
// Check if an incoming prop key is a declared emit event listener.
|
||||||
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
|
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
|
||||||
// both considered matched listeners.
|
// both considered matched listeners.
|
||||||
export function isEmitListener(comp: ConcreteComponent, key: string): boolean {
|
export function isEmitListener(
|
||||||
let emits: ObjectEmitsOptions | undefined
|
options: ObjectEmitsOptions | null,
|
||||||
if (!isOn(key) || !(emits = normalizeEmitsOptions(comp))) {
|
key: string
|
||||||
|
): boolean {
|
||||||
|
if (!options || !isOn(key)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
key = key.replace(/Once$/, '')
|
key = key.replace(/Once$/, '')
|
||||||
return (
|
return (
|
||||||
hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
|
hasOwn(options, key[2].toLowerCase() + key.slice(3)) ||
|
||||||
hasOwn(emits, key.slice(2))
|
hasOwn(options, key.slice(2))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,7 @@ import {
|
|||||||
WritableComputedOptions,
|
WritableComputedOptions,
|
||||||
toRaw
|
toRaw
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import {
|
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
ComponentObjectPropsOptions,
|
|
||||||
ExtractPropTypes,
|
|
||||||
normalizePropsOptions
|
|
||||||
} from './componentProps'
|
|
||||||
import { EmitsOptions } from './componentEmits'
|
import { EmitsOptions } from './componentEmits'
|
||||||
import { Directive } from './directives'
|
import { Directive } from './directives'
|
||||||
import {
|
import {
|
||||||
@ -431,7 +427,7 @@ export function applyOptions(
|
|||||||
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const propsOptions = normalizePropsOptions(options)[0]
|
const [propsOptions] = instance.propsOptions
|
||||||
if (propsOptions) {
|
if (propsOptions) {
|
||||||
for (const key in propsOptions) {
|
for (const key in propsOptions) {
|
||||||
checkDuplicateProperties!(OptionTypes.PROPS, key)
|
checkDuplicateProperties!(OptionTypes.PROPS, key)
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
} from './component'
|
} from './component'
|
||||||
import { isEmitListener } from './componentEmits'
|
import { isEmitListener } from './componentEmits'
|
||||||
import { InternalObjectKey } from './vnode'
|
import { InternalObjectKey } from './vnode'
|
||||||
|
import { AppContext } from './apiCreateApp'
|
||||||
|
|
||||||
export type ComponentPropsOptions<P = Data> =
|
export type ComponentPropsOptions<P = Data> =
|
||||||
| ComponentObjectPropsOptions<P>
|
| ComponentObjectPropsOptions<P>
|
||||||
@ -107,7 +108,8 @@ type NormalizedProp =
|
|||||||
|
|
||||||
// normalized value is a tuple of the actual normalized options
|
// normalized value is a tuple of the actual normalized options
|
||||||
// and an array of prop keys that need value casting (booleans and defaults)
|
// and an array of prop keys that need value casting (booleans and defaults)
|
||||||
export type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
|
export type NormalizedProps = Record<string, NormalizedProp>
|
||||||
|
export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
|
||||||
|
|
||||||
export function initProps(
|
export function initProps(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
@ -121,7 +123,7 @@ export function initProps(
|
|||||||
setFullProps(instance, rawProps, props, attrs)
|
setFullProps(instance, rawProps, props, attrs)
|
||||||
// validation
|
// validation
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validateProps(props, instance.type)
|
validateProps(props, instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStateful) {
|
if (isStateful) {
|
||||||
@ -151,7 +153,7 @@ export function updateProps(
|
|||||||
vnode: { patchFlag }
|
vnode: { patchFlag }
|
||||||
} = instance
|
} = instance
|
||||||
const rawCurrentProps = toRaw(props)
|
const rawCurrentProps = toRaw(props)
|
||||||
const [options] = normalizePropsOptions(instance.type)
|
const [options] = instance.propsOptions
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// always force full diff if hmr is enabled
|
// always force full diff if hmr is enabled
|
||||||
@ -236,7 +238,7 @@ export function updateProps(
|
|||||||
trigger(instance, TriggerOpTypes.SET, '$attrs')
|
trigger(instance, TriggerOpTypes.SET, '$attrs')
|
||||||
|
|
||||||
if (__DEV__ && rawProps) {
|
if (__DEV__ && rawProps) {
|
||||||
validateProps(props, instance.type)
|
validateProps(props, instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +248,7 @@ function setFullProps(
|
|||||||
props: Data,
|
props: Data,
|
||||||
attrs: Data
|
attrs: Data
|
||||||
) {
|
) {
|
||||||
const [options, needCastKeys] = normalizePropsOptions(instance.type)
|
const [options, needCastKeys] = instance.propsOptions
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
for (const key in rawProps) {
|
for (const key in rawProps) {
|
||||||
const value = rawProps[key]
|
const value = rawProps[key]
|
||||||
@ -259,7 +261,7 @@ function setFullProps(
|
|||||||
let camelKey
|
let camelKey
|
||||||
if (options && hasOwn(options, (camelKey = camelize(key)))) {
|
if (options && hasOwn(options, (camelKey = camelize(key)))) {
|
||||||
props[camelKey] = value
|
props[camelKey] = value
|
||||||
} else if (!isEmitListener(instance.type, key)) {
|
} else if (!isEmitListener(instance.emitsOptions, key)) {
|
||||||
// Any non-declared (either as a prop or an emitted event) props are put
|
// Any non-declared (either as a prop or an emitted event) props are put
|
||||||
// into a separate `attrs` object for spreading. Make sure to preserve
|
// into a separate `attrs` object for spreading. Make sure to preserve
|
||||||
// original key casing
|
// original key casing
|
||||||
@ -283,7 +285,7 @@ function setFullProps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolvePropValue(
|
function resolvePropValue(
|
||||||
options: NormalizedPropsOptions[0],
|
options: NormalizedProps,
|
||||||
props: Data,
|
props: Data,
|
||||||
key: string,
|
key: string,
|
||||||
value: unknown
|
value: unknown
|
||||||
@ -315,10 +317,15 @@ function resolvePropValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizePropsOptions(
|
export function normalizePropsOptions(
|
||||||
comp: ConcreteComponent
|
comp: ConcreteComponent,
|
||||||
): NormalizedPropsOptions | [] {
|
appContext: AppContext,
|
||||||
if (comp.__props) {
|
asMixin = false
|
||||||
return comp.__props
|
): NormalizedPropsOptions {
|
||||||
|
const appId = appContext.app ? appContext.app._uid : -1
|
||||||
|
const cache = comp.__props || (comp.__props = {})
|
||||||
|
const cached = cache[appId]
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = comp.props
|
const raw = comp.props
|
||||||
@ -329,22 +336,24 @@ export function normalizePropsOptions(
|
|||||||
let hasExtends = false
|
let hasExtends = false
|
||||||
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
||||||
const extendProps = (raw: ComponentOptions) => {
|
const extendProps = (raw: ComponentOptions) => {
|
||||||
const [props, keys] = normalizePropsOptions(raw)
|
hasExtends = true
|
||||||
|
const [props, keys] = normalizePropsOptions(raw, appContext, true)
|
||||||
extend(normalized, props)
|
extend(normalized, props)
|
||||||
if (keys) needCastKeys.push(...keys)
|
if (keys) needCastKeys.push(...keys)
|
||||||
}
|
}
|
||||||
|
if (!asMixin && appContext.mixins.length) {
|
||||||
|
appContext.mixins.forEach(extendProps)
|
||||||
|
}
|
||||||
if (comp.extends) {
|
if (comp.extends) {
|
||||||
hasExtends = true
|
|
||||||
extendProps(comp.extends)
|
extendProps(comp.extends)
|
||||||
}
|
}
|
||||||
if (comp.mixins) {
|
if (comp.mixins) {
|
||||||
hasExtends = true
|
|
||||||
comp.mixins.forEach(extendProps)
|
comp.mixins.forEach(extendProps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!raw && !hasExtends) {
|
if (!raw && !hasExtends) {
|
||||||
return (comp.__props = EMPTY_ARR)
|
return (cache[appId] = EMPTY_ARR)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(raw)) {
|
if (isArray(raw)) {
|
||||||
@ -381,9 +390,8 @@ export function normalizePropsOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys]
|
|
||||||
comp.__props = normalizedEntry
|
return (cache[appId] = [normalized, needCastKeys])
|
||||||
return normalizedEntry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use function string name to check type constructors
|
// use function string name to check type constructors
|
||||||
@ -416,9 +424,9 @@ function getTypeIndex(
|
|||||||
/**
|
/**
|
||||||
* dev only
|
* dev only
|
||||||
*/
|
*/
|
||||||
function validateProps(props: Data, comp: ConcreteComponent) {
|
function validateProps(props: Data, instance: ComponentInternalInstance) {
|
||||||
const rawValues = toRaw(props)
|
const rawValues = toRaw(props)
|
||||||
const options = normalizePropsOptions(comp)[0]
|
const options = instance.propsOptions[0]
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
let opt = options[key]
|
let opt = options[key]
|
||||||
if (opt == null) continue
|
if (opt == null) continue
|
||||||
|
@ -29,7 +29,6 @@ import {
|
|||||||
resolveMergedOptions,
|
resolveMergedOptions,
|
||||||
isInBeforeCreate
|
isInBeforeCreate
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
|
||||||
import { EmitsOptions, EmitFn } from './componentEmits'
|
import { EmitsOptions, EmitFn } from './componentEmits'
|
||||||
import { Slots } from './componentSlots'
|
import { Slots } from './componentSlots'
|
||||||
import {
|
import {
|
||||||
@ -250,7 +249,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
} else if (
|
} else if (
|
||||||
// only cache other properties when instance has declared (thus stable)
|
// only cache other properties when instance has declared (thus stable)
|
||||||
// props
|
// props
|
||||||
(normalizedProps = normalizePropsOptions(type)[0]) &&
|
(normalizedProps = instance.propsOptions[0]) &&
|
||||||
hasOwn(normalizedProps, key)
|
hasOwn(normalizedProps, key)
|
||||||
) {
|
) {
|
||||||
accessCache![key] = AccessTypes.PROPS
|
accessCache![key] = AccessTypes.PROPS
|
||||||
@ -354,7 +353,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
|
|
||||||
has(
|
has(
|
||||||
{
|
{
|
||||||
_: { data, setupState, accessCache, ctx, type, appContext }
|
_: { data, setupState, accessCache, ctx, appContext, propsOptions }
|
||||||
}: ComponentRenderContext,
|
}: ComponentRenderContext,
|
||||||
key: string
|
key: string
|
||||||
) {
|
) {
|
||||||
@ -363,8 +362,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||||||
accessCache![key] !== undefined ||
|
accessCache![key] !== undefined ||
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
|
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
|
||||||
((normalizedProps = normalizePropsOptions(type)[0]) &&
|
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
||||||
hasOwn(normalizedProps, key)) ||
|
|
||||||
hasOwn(ctx, key) ||
|
hasOwn(ctx, key) ||
|
||||||
hasOwn(publicPropertiesMap, key) ||
|
hasOwn(publicPropertiesMap, key) ||
|
||||||
hasOwn(appContext.config.globalProperties, key)
|
hasOwn(appContext.config.globalProperties, key)
|
||||||
@ -450,8 +448,10 @@ export function createRenderContext(instance: ComponentInternalInstance) {
|
|||||||
export function exposePropsOnRenderContext(
|
export function exposePropsOnRenderContext(
|
||||||
instance: ComponentInternalInstance
|
instance: ComponentInternalInstance
|
||||||
) {
|
) {
|
||||||
const { ctx, type } = instance
|
const {
|
||||||
const propsOptions = normalizePropsOptions(type)[0]
|
ctx,
|
||||||
|
propsOptions: [propsOptions]
|
||||||
|
} = instance
|
||||||
if (propsOptions) {
|
if (propsOptions) {
|
||||||
Object.keys(propsOptions).forEach(key => {
|
Object.keys(propsOptions).forEach(key => {
|
||||||
Object.defineProperty(ctx, key, {
|
Object.defineProperty(ctx, key, {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user