fix(runtime-core): respect props from mixins and extends
fix #1236, close #1250
This commit is contained in:
parent
dc986addd9
commit
2417a0cb30
@ -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>
|
||||
|
@ -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 ` +
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user