From 2417a0cb302ed72e145986f85422470713edf2d8 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 9 Jun 2020 11:27:40 -0400 Subject: [PATCH] fix(runtime-core): respect props from mixins and extends fix #1236, close #1250 --- packages/runtime-core/src/component.ts | 14 ++- packages/runtime-core/src/componentEmits.ts | 2 +- packages/runtime-core/src/componentOptions.ts | 14 +-- packages/runtime-core/src/componentProps.ts | 93 ++++++++++++++----- packages/runtime-core/src/componentProxy.ts | 12 +-- 5 files changed, 95 insertions(+), 40 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 522e64570d4..94f972d14b1 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -15,7 +15,11 @@ import { exposePropsOnRenderContext, exposeSetupStateOnRenderContext } from './componentProxy' -import { ComponentPropsOptions, initProps } from './componentProps' +import { + ComponentPropsOptions, + NormalizedPropsOptions, + initProps +} from './componentProps' import { Slots, initSlots, InternalSlots } from './componentSlots' import { warn } from './warning' import { ErrorCodes, callWithErrorHandling } from './errorHandling' @@ -50,7 +54,11 @@ export type Data = { [key: string]: unknown } // Note: can't mark this whole interface internal because some public interfaces // extend it. -export interface SFCInternalOptions { +export interface ComponentInternalOptions { + /** + * @internal + */ + __props?: NormalizedPropsOptions | [] /** * @internal */ @@ -76,7 +84,7 @@ export interface SFCInternalOptions { export interface FunctionalComponent< P = {}, E extends EmitsOptions = Record -> extends SFCInternalOptions { +> extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor (props: P, ctx: SetupContext): any props?: ComponentPropsOptions

diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 28a6af654e5..7bb20f73e2e 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -44,7 +44,7 @@ export function emit( const options = normalizeEmitsOptions(instance.type.emits) if (options) { if (!(event in options)) { - const propsOptions = normalizePropsOptions(instance.type.props)[0] + const propsOptions = normalizePropsOptions(instance.type)[0] if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) { warn( `Component emitted event "${event}" but it is neither declared in ` + diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 0cab6fc0cc8..54be5467a27 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -2,7 +2,7 @@ import { ComponentInternalInstance, Data, SetupContext, - SFCInternalOptions, + ComponentInternalOptions, PublicAPIComponent, Component } from './component' @@ -87,7 +87,7 @@ export interface ComponentOptionsBase< EE extends string = string > extends LegacyOptions, - SFCInternalOptions, + ComponentInternalOptions, ComponentCustomOptions { setup?: ( this: void, @@ -367,7 +367,6 @@ export function applyOptions( mixins, extends: extendsOptions, // state - props: propsOptions, data: dataOptions, computed: computedOptions, methods, @@ -413,9 +412,12 @@ export function applyOptions( const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null - if (__DEV__ && propsOptions) { - for (const key in normalizePropsOptions(propsOptions)[0]) { - checkDuplicateProperties!(OptionTypes.PROPS, key) + if (__DEV__) { + const propsOptions = normalizePropsOptions(options)[0] + if (propsOptions) { + for (const key in propsOptions) { + checkDuplicateProperties!(OptionTypes.PROPS, key) + } } } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 472d4dbaf0a..5c3c1f3669d 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -17,7 +17,12 @@ import { def } from '@vue/shared' import { warn } from './warning' -import { Data, ComponentInternalInstance } from './component' +import { + Data, + ComponentInternalInstance, + ComponentOptions, + Component +} from './component' import { isEmitListener } from './componentEmits' import { InternalObjectKey } from './vnode' @@ -96,7 +101,7 @@ type NormalizedProp = // normalized value is a tuple of the actual normalized options // and an array of prop keys that need value casting (booleans and defaults) -type NormalizedPropsOptions = [Record, string[]] +export type NormalizedPropsOptions = [Record, string[]] export function initProps( instance: ComponentInternalInstance, @@ -108,17 +113,16 @@ export function initProps( const attrs: Data = {} def(attrs, InternalObjectKey, 1) setFullProps(instance, rawProps, props, attrs) - const options = instance.type.props // validation - if (__DEV__ && options && rawProps) { - validateProps(props, options) + if (__DEV__) { + validateProps(props, instance.type) } if (isStateful) { // stateful instance.props = isSSR ? props : shallowReactive(props) } else { - if (!options) { + if (!instance.type.props) { // functional w/ optional props, props === attrs instance.props = attrs } else { @@ -140,9 +144,8 @@ export function updateProps( attrs, vnode: { patchFlag } } = instance - const rawOptions = instance.type.props const rawCurrentProps = toRaw(props) - const [options] = normalizePropsOptions(rawOptions) + const [options] = normalizePropsOptions(instance.type) if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) { if (patchFlag & PatchFlags.PROPS) { @@ -211,8 +214,8 @@ export function updateProps( } } - if (__DEV__ && rawOptions && rawProps) { - validateProps(props, rawOptions) + if (__DEV__ && rawProps) { + validateProps(props, instance.type) } } @@ -222,9 +225,7 @@ function setFullProps( props: Data, attrs: Data ) { - const [options, needCastKeys] = normalizePropsOptions( - instance.type.props - ) + const [options, needCastKeys] = normalizePropsOptions(instance.type) const emits = instance.type.emits if (rawProps) { @@ -292,16 +293,38 @@ function resolvePropValue( } export function normalizePropsOptions( - raw: ComponentPropsOptions | undefined + comp: Component ): NormalizedPropsOptions | [] { - if (!raw) { - return EMPTY_ARR as any - } - if ((raw as any)._n) { - return (raw as any)._n + if (comp.__props) { + return comp.__props } + + const raw = comp.props const normalized: NormalizedPropsOptions[0] = {} const needCastKeys: NormalizedPropsOptions[1] = [] + + // apply mixin/extends props + let hasExtends = false + if (__FEATURE_OPTIONS__ && !isFunction(comp)) { + const extendProps = (raw: ComponentOptions) => { + const [props, keys] = normalizePropsOptions(raw) + Object.assign(normalized, props) + if (keys) needCastKeys.push(...keys) + } + if (comp.extends) { + hasExtends = true + extendProps(comp.extends) + } + if (comp.mixins) { + hasExtends = true + comp.mixins.forEach(extendProps) + } + } + + if (!raw && !hasExtends) { + return (comp.__props = EMPTY_ARR) + } + if (isArray(raw)) { for (let i = 0; i < raw.length; i++) { if (__DEV__ && !isString(raw[i])) { @@ -312,7 +335,7 @@ export function normalizePropsOptions( normalized[normalizedKey] = EMPTY_OBJ } } - } else { + } else if (raw) { if (__DEV__ && !isObject(raw)) { warn(`invalid props options`, raw) } @@ -337,7 +360,7 @@ export function normalizePropsOptions( } } const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys] - def(raw, '_n', normalizedEntry) + comp.__props = normalizedEntry return normalizedEntry } @@ -368,9 +391,12 @@ function getTypeIndex( return -1 } -function validateProps(props: Data, rawOptions: ComponentPropsOptions) { +/** + * dev only + */ +function validateProps(props: Data, comp: Component) { const rawValues = toRaw(props) - const options = normalizePropsOptions(rawOptions)[0] + const options = normalizePropsOptions(comp)[0] for (const key in options) { let opt = options[key] if (opt == null) continue @@ -378,6 +404,9 @@ function validateProps(props: Data, rawOptions: ComponentPropsOptions) { } } +/** + * dev only + */ function validatePropName(key: string) { if (key[0] !== '$') { return true @@ -387,6 +416,9 @@ function validatePropName(key: string) { return false } +/** + * dev only + */ function validateProp( name: string, value: unknown, @@ -434,6 +466,9 @@ type AssertionResult = { expectedType: string } +/** + * dev only + */ function assertType(value: unknown, type: PropConstructor): AssertionResult { let valid const expectedType = getType(type) @@ -457,6 +492,9 @@ function assertType(value: unknown, type: PropConstructor): AssertionResult { } } +/** + * dev only + */ function getInvalidTypeMessage( name: string, value: unknown, @@ -485,6 +523,9 @@ function getInvalidTypeMessage( return message } +/** + * dev only + */ function styleValue(value: unknown, type: string): string { if (type === 'String') { return `"${value}"` @@ -495,11 +536,17 @@ function styleValue(value: unknown, type: string): string { } } +/** + * dev only + */ function isExplicable(type: string): boolean { const explicitTypes = ['string', 'number', 'boolean'] return explicitTypes.some(elem => type.toLowerCase() === elem) } +/** + * dev only + */ function isBoolean(...args: string[]): boolean { return args.some(elem => elem.toLowerCase() === 'boolean') } diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index 01c31897546..73a2d1541fd 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -238,7 +238,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler = { // only cache other properties when instance has declared (thus stable) // props type.props && - hasOwn(normalizePropsOptions(type.props)[0]!, key) + hasOwn(normalizePropsOptions(type)[0]!, key) ) { accessCache![key] = AccessTypes.PROPS return props![key] @@ -347,7 +347,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler = { accessCache![key] !== undefined || (data !== EMPTY_OBJ && hasOwn(data, key)) || (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) || - (type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) || + (type.props && hasOwn(normalizePropsOptions(type)[0]!, key)) || hasOwn(ctx, key) || hasOwn(publicPropertiesMap, key) || hasOwn(appContext.config.globalProperties, key) @@ -430,12 +430,10 @@ export function createRenderContext(instance: ComponentInternalInstance) { export function exposePropsOnRenderContext( instance: ComponentInternalInstance ) { - const { - ctx, - type: { props: propsOptions } - } = instance + const { ctx, type } = instance + const propsOptions = normalizePropsOptions(type)[0] if (propsOptions) { - Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => { + Object.keys(propsOptions).forEach(key => { Object.defineProperty(ctx, key, { enumerable: true, configurable: true,