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