fix(runtime-core): respect props from mixins and extends

fix #1236, close #1250
This commit is contained in:
Evan You 2020-06-09 11:27:40 -04:00
parent dc986addd9
commit 2417a0cb30
5 changed files with 95 additions and 40 deletions

View File

@ -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<string, any>
> extends SFCInternalOptions {
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(props: P, ctx: SetupContext<E>): any
props?: ComponentPropsOptions<P>

View File

@ -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 ` +

View File

@ -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<Props, D, C, M, Mixin, Extends>,
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)
}
}
}

View File

@ -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, NormalizedProp>, string[]]
export type NormalizedPropsOptions = [Record<string, NormalizedProp>, 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')
}

View File

@ -238,7 +238,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
// 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<any> = {
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,