Skip to content

Commit 2417a0c

Browse files
committed
fix(runtime-core): respect props from mixins and extends
fix #1236, close #1250
1 parent dc986ad commit 2417a0c

File tree

5 files changed

+95
-40
lines changed

5 files changed

+95
-40
lines changed

packages/runtime-core/src/component.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import {
1515
exposePropsOnRenderContext,
1616
exposeSetupStateOnRenderContext
1717
} from './componentProxy'
18-
import { ComponentPropsOptions, initProps } from './componentProps'
18+
import {
19+
ComponentPropsOptions,
20+
NormalizedPropsOptions,
21+
initProps
22+
} from './componentProps'
1923
import { Slots, initSlots, InternalSlots } from './componentSlots'
2024
import { warn } from './warning'
2125
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
@@ -50,7 +54,11 @@ export type Data = { [key: string]: unknown }
5054

5155
// Note: can't mark this whole interface internal because some public interfaces
5256
// extend it.
53-
export interface SFCInternalOptions {
57+
export interface ComponentInternalOptions {
58+
/**
59+
* @internal
60+
*/
61+
__props?: NormalizedPropsOptions | []
5462
/**
5563
* @internal
5664
*/
@@ -76,7 +84,7 @@ export interface SFCInternalOptions {
7684
export interface FunctionalComponent<
7785
P = {},
7886
E extends EmitsOptions = Record<string, any>
79-
> extends SFCInternalOptions {
87+
> extends ComponentInternalOptions {
8088
// use of any here is intentional so it can be a valid JSX Element constructor
8189
(props: P, ctx: SetupContext<E>): any
8290
props?: ComponentPropsOptions<P>

packages/runtime-core/src/componentEmits.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function emit(
4444
const options = normalizeEmitsOptions(instance.type.emits)
4545
if (options) {
4646
if (!(event in options)) {
47-
const propsOptions = normalizePropsOptions(instance.type.props)[0]
47+
const propsOptions = normalizePropsOptions(instance.type)[0]
4848
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
4949
warn(
5050
`Component emitted event "${event}" but it is neither declared in ` +

packages/runtime-core/src/componentOptions.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
ComponentInternalInstance,
33
Data,
44
SetupContext,
5-
SFCInternalOptions,
5+
ComponentInternalOptions,
66
PublicAPIComponent,
77
Component
88
} from './component'
@@ -87,7 +87,7 @@ export interface ComponentOptionsBase<
8787
EE extends string = string
8888
>
8989
extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
90-
SFCInternalOptions,
90+
ComponentInternalOptions,
9191
ComponentCustomOptions {
9292
setup?: (
9393
this: void,
@@ -367,7 +367,6 @@ export function applyOptions(
367367
mixins,
368368
extends: extendsOptions,
369369
// state
370-
props: propsOptions,
371370
data: dataOptions,
372371
computed: computedOptions,
373372
methods,
@@ -413,9 +412,12 @@ export function applyOptions(
413412

414413
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
415414

416-
if (__DEV__ && propsOptions) {
417-
for (const key in normalizePropsOptions(propsOptions)[0]) {
418-
checkDuplicateProperties!(OptionTypes.PROPS, key)
415+
if (__DEV__) {
416+
const propsOptions = normalizePropsOptions(options)[0]
417+
if (propsOptions) {
418+
for (const key in propsOptions) {
419+
checkDuplicateProperties!(OptionTypes.PROPS, key)
420+
}
419421
}
420422
}
421423

packages/runtime-core/src/componentProps.ts

+70-23
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ import {
1717
def
1818
} from '@vue/shared'
1919
import { warn } from './warning'
20-
import { Data, ComponentInternalInstance } from './component'
20+
import {
21+
Data,
22+
ComponentInternalInstance,
23+
ComponentOptions,
24+
Component
25+
} from './component'
2126
import { isEmitListener } from './componentEmits'
2227
import { InternalObjectKey } from './vnode'
2328

@@ -96,7 +101,7 @@ type NormalizedProp =
96101

97102
// normalized value is a tuple of the actual normalized options
98103
// and an array of prop keys that need value casting (booleans and defaults)
99-
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
104+
export type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
100105

101106
export function initProps(
102107
instance: ComponentInternalInstance,
@@ -108,17 +113,16 @@ export function initProps(
108113
const attrs: Data = {}
109114
def(attrs, InternalObjectKey, 1)
110115
setFullProps(instance, rawProps, props, attrs)
111-
const options = instance.type.props
112116
// validation
113-
if (__DEV__ && options && rawProps) {
114-
validateProps(props, options)
117+
if (__DEV__) {
118+
validateProps(props, instance.type)
115119
}
116120

117121
if (isStateful) {
118122
// stateful
119123
instance.props = isSSR ? props : shallowReactive(props)
120124
} else {
121-
if (!options) {
125+
if (!instance.type.props) {
122126
// functional w/ optional props, props === attrs
123127
instance.props = attrs
124128
} else {
@@ -140,9 +144,8 @@ export function updateProps(
140144
attrs,
141145
vnode: { patchFlag }
142146
} = instance
143-
const rawOptions = instance.type.props
144147
const rawCurrentProps = toRaw(props)
145-
const [options] = normalizePropsOptions(rawOptions)
148+
const [options] = normalizePropsOptions(instance.type)
146149

147150
if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) {
148151
if (patchFlag & PatchFlags.PROPS) {
@@ -211,8 +214,8 @@ export function updateProps(
211214
}
212215
}
213216

214-
if (__DEV__ && rawOptions && rawProps) {
215-
validateProps(props, rawOptions)
217+
if (__DEV__ && rawProps) {
218+
validateProps(props, instance.type)
216219
}
217220
}
218221

@@ -222,9 +225,7 @@ function setFullProps(
222225
props: Data,
223226
attrs: Data
224227
) {
225-
const [options, needCastKeys] = normalizePropsOptions(
226-
instance.type.props
227-
)
228+
const [options, needCastKeys] = normalizePropsOptions(instance.type)
228229
const emits = instance.type.emits
229230

230231
if (rawProps) {
@@ -292,16 +293,38 @@ function resolvePropValue(
292293
}
293294

294295
export function normalizePropsOptions(
295-
raw: ComponentPropsOptions | undefined
296+
comp: Component
296297
): NormalizedPropsOptions | [] {
297-
if (!raw) {
298-
return EMPTY_ARR as any
299-
}
300-
if ((raw as any)._n) {
301-
return (raw as any)._n
298+
if (comp.__props) {
299+
return comp.__props
302300
}
301+
302+
const raw = comp.props
303303
const normalized: NormalizedPropsOptions[0] = {}
304304
const needCastKeys: NormalizedPropsOptions[1] = []
305+
306+
// apply mixin/extends props
307+
let hasExtends = false
308+
if (__FEATURE_OPTIONS__ && !isFunction(comp)) {
309+
const extendProps = (raw: ComponentOptions) => {
310+
const [props, keys] = normalizePropsOptions(raw)
311+
Object.assign(normalized, props)
312+
if (keys) needCastKeys.push(...keys)
313+
}
314+
if (comp.extends) {
315+
hasExtends = true
316+
extendProps(comp.extends)
317+
}
318+
if (comp.mixins) {
319+
hasExtends = true
320+
comp.mixins.forEach(extendProps)
321+
}
322+
}
323+
324+
if (!raw && !hasExtends) {
325+
return (comp.__props = EMPTY_ARR)
326+
}
327+
305328
if (isArray(raw)) {
306329
for (let i = 0; i < raw.length; i++) {
307330
if (__DEV__ && !isString(raw[i])) {
@@ -312,7 +335,7 @@ export function normalizePropsOptions(
312335
normalized[normalizedKey] = EMPTY_OBJ
313336
}
314337
}
315-
} else {
338+
} else if (raw) {
316339
if (__DEV__ && !isObject(raw)) {
317340
warn(`invalid props options`, raw)
318341
}
@@ -337,7 +360,7 @@ export function normalizePropsOptions(
337360
}
338361
}
339362
const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys]
340-
def(raw, '_n', normalizedEntry)
363+
comp.__props = normalizedEntry
341364
return normalizedEntry
342365
}
343366

@@ -368,16 +391,22 @@ function getTypeIndex(
368391
return -1
369392
}
370393

371-
function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
394+
/**
395+
* dev only
396+
*/
397+
function validateProps(props: Data, comp: Component) {
372398
const rawValues = toRaw(props)
373-
const options = normalizePropsOptions(rawOptions)[0]
399+
const options = normalizePropsOptions(comp)[0]
374400
for (const key in options) {
375401
let opt = options[key]
376402
if (opt == null) continue
377403
validateProp(key, rawValues[key], opt, !hasOwn(rawValues, key))
378404
}
379405
}
380406

407+
/**
408+
* dev only
409+
*/
381410
function validatePropName(key: string) {
382411
if (key[0] !== '$') {
383412
return true
@@ -387,6 +416,9 @@ function validatePropName(key: string) {
387416
return false
388417
}
389418

419+
/**
420+
* dev only
421+
*/
390422
function validateProp(
391423
name: string,
392424
value: unknown,
@@ -434,6 +466,9 @@ type AssertionResult = {
434466
expectedType: string
435467
}
436468

469+
/**
470+
* dev only
471+
*/
437472
function assertType(value: unknown, type: PropConstructor): AssertionResult {
438473
let valid
439474
const expectedType = getType(type)
@@ -457,6 +492,9 @@ function assertType(value: unknown, type: PropConstructor): AssertionResult {
457492
}
458493
}
459494

495+
/**
496+
* dev only
497+
*/
460498
function getInvalidTypeMessage(
461499
name: string,
462500
value: unknown,
@@ -485,6 +523,9 @@ function getInvalidTypeMessage(
485523
return message
486524
}
487525

526+
/**
527+
* dev only
528+
*/
488529
function styleValue(value: unknown, type: string): string {
489530
if (type === 'String') {
490531
return `"${value}"`
@@ -495,11 +536,17 @@ function styleValue(value: unknown, type: string): string {
495536
}
496537
}
497538

539+
/**
540+
* dev only
541+
*/
498542
function isExplicable(type: string): boolean {
499543
const explicitTypes = ['string', 'number', 'boolean']
500544
return explicitTypes.some(elem => type.toLowerCase() === elem)
501545
}
502546

547+
/**
548+
* dev only
549+
*/
503550
function isBoolean(...args: string[]): boolean {
504551
return args.some(elem => elem.toLowerCase() === 'boolean')
505552
}

packages/runtime-core/src/componentProxy.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
238238
// only cache other properties when instance has declared (thus stable)
239239
// props
240240
type.props &&
241-
hasOwn(normalizePropsOptions(type.props)[0]!, key)
241+
hasOwn(normalizePropsOptions(type)[0]!, key)
242242
) {
243243
accessCache![key] = AccessTypes.PROPS
244244
return props![key]
@@ -347,7 +347,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
347347
accessCache![key] !== undefined ||
348348
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
349349
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
350-
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
350+
(type.props && hasOwn(normalizePropsOptions(type)[0]!, key)) ||
351351
hasOwn(ctx, key) ||
352352
hasOwn(publicPropertiesMap, key) ||
353353
hasOwn(appContext.config.globalProperties, key)
@@ -430,12 +430,10 @@ export function createRenderContext(instance: ComponentInternalInstance) {
430430
export function exposePropsOnRenderContext(
431431
instance: ComponentInternalInstance
432432
) {
433-
const {
434-
ctx,
435-
type: { props: propsOptions }
436-
} = instance
433+
const { ctx, type } = instance
434+
const propsOptions = normalizePropsOptions(type)[0]
437435
if (propsOptions) {
438-
Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
436+
Object.keys(propsOptions).forEach(key => {
439437
Object.defineProperty(ctx, key, {
440438
enumerable: true,
441439
configurable: true,

0 commit comments

Comments
 (0)