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,
|
exposePropsOnRenderContext,
|
||||||
exposeSetupStateOnRenderContext
|
exposeSetupStateOnRenderContext
|
||||||
} from './componentProxy'
|
} from './componentProxy'
|
||||||
import { ComponentPropsOptions, initProps } from './componentProps'
|
import {
|
||||||
|
ComponentPropsOptions,
|
||||||
|
NormalizedPropsOptions,
|
||||||
|
initProps
|
||||||
|
} from './componentProps'
|
||||||
import { Slots, initSlots, InternalSlots } from './componentSlots'
|
import { Slots, initSlots, InternalSlots } from './componentSlots'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
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
|
// Note: can't mark this whole interface internal because some public interfaces
|
||||||
// extend it.
|
// extend it.
|
||||||
export interface SFCInternalOptions {
|
export interface ComponentInternalOptions {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
__props?: NormalizedPropsOptions | []
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -76,7 +84,7 @@ export interface SFCInternalOptions {
|
|||||||
export interface FunctionalComponent<
|
export interface FunctionalComponent<
|
||||||
P = {},
|
P = {},
|
||||||
E extends EmitsOptions = Record<string, any>
|
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
|
// use of any here is intentional so it can be a valid JSX Element constructor
|
||||||
(props: P, ctx: SetupContext<E>): any
|
(props: P, ctx: SetupContext<E>): any
|
||||||
props?: ComponentPropsOptions<P>
|
props?: ComponentPropsOptions<P>
|
||||||
|
@ -44,7 +44,7 @@ export function emit(
|
|||||||
const options = normalizeEmitsOptions(instance.type.emits)
|
const options = normalizeEmitsOptions(instance.type.emits)
|
||||||
if (options) {
|
if (options) {
|
||||||
if (!(event in 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)) {
|
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 ` +
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
Data,
|
Data,
|
||||||
SetupContext,
|
SetupContext,
|
||||||
SFCInternalOptions,
|
ComponentInternalOptions,
|
||||||
PublicAPIComponent,
|
PublicAPIComponent,
|
||||||
Component
|
Component
|
||||||
} from './component'
|
} from './component'
|
||||||
@ -87,7 +87,7 @@ export interface ComponentOptionsBase<
|
|||||||
EE extends string = string
|
EE extends string = string
|
||||||
>
|
>
|
||||||
extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
|
extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
|
||||||
SFCInternalOptions,
|
ComponentInternalOptions,
|
||||||
ComponentCustomOptions {
|
ComponentCustomOptions {
|
||||||
setup?: (
|
setup?: (
|
||||||
this: void,
|
this: void,
|
||||||
@ -367,7 +367,6 @@ export function applyOptions(
|
|||||||
mixins,
|
mixins,
|
||||||
extends: extendsOptions,
|
extends: extendsOptions,
|
||||||
// state
|
// state
|
||||||
props: propsOptions,
|
|
||||||
data: dataOptions,
|
data: dataOptions,
|
||||||
computed: computedOptions,
|
computed: computedOptions,
|
||||||
methods,
|
methods,
|
||||||
@ -413,9 +412,12 @@ export function applyOptions(
|
|||||||
|
|
||||||
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
|
||||||
|
|
||||||
if (__DEV__ && propsOptions) {
|
if (__DEV__) {
|
||||||
for (const key in normalizePropsOptions(propsOptions)[0]) {
|
const propsOptions = normalizePropsOptions(options)[0]
|
||||||
checkDuplicateProperties!(OptionTypes.PROPS, key)
|
if (propsOptions) {
|
||||||
|
for (const key in propsOptions) {
|
||||||
|
checkDuplicateProperties!(OptionTypes.PROPS, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,12 @@ import {
|
|||||||
def
|
def
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { Data, ComponentInternalInstance } from './component'
|
import {
|
||||||
|
Data,
|
||||||
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
|
Component
|
||||||
|
} from './component'
|
||||||
import { isEmitListener } from './componentEmits'
|
import { isEmitListener } from './componentEmits'
|
||||||
import { InternalObjectKey } from './vnode'
|
import { InternalObjectKey } from './vnode'
|
||||||
|
|
||||||
@ -96,7 +101,7 @@ 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)
|
||||||
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
|
export type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
|
||||||
|
|
||||||
export function initProps(
|
export function initProps(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
@ -108,17 +113,16 @@ export function initProps(
|
|||||||
const attrs: Data = {}
|
const attrs: Data = {}
|
||||||
def(attrs, InternalObjectKey, 1)
|
def(attrs, InternalObjectKey, 1)
|
||||||
setFullProps(instance, rawProps, props, attrs)
|
setFullProps(instance, rawProps, props, attrs)
|
||||||
const options = instance.type.props
|
|
||||||
// validation
|
// validation
|
||||||
if (__DEV__ && options && rawProps) {
|
if (__DEV__) {
|
||||||
validateProps(props, options)
|
validateProps(props, instance.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStateful) {
|
if (isStateful) {
|
||||||
// stateful
|
// stateful
|
||||||
instance.props = isSSR ? props : shallowReactive(props)
|
instance.props = isSSR ? props : shallowReactive(props)
|
||||||
} else {
|
} else {
|
||||||
if (!options) {
|
if (!instance.type.props) {
|
||||||
// functional w/ optional props, props === attrs
|
// functional w/ optional props, props === attrs
|
||||||
instance.props = attrs
|
instance.props = attrs
|
||||||
} else {
|
} else {
|
||||||
@ -140,9 +144,8 @@ export function updateProps(
|
|||||||
attrs,
|
attrs,
|
||||||
vnode: { patchFlag }
|
vnode: { patchFlag }
|
||||||
} = instance
|
} = instance
|
||||||
const rawOptions = instance.type.props
|
|
||||||
const rawCurrentProps = toRaw(props)
|
const rawCurrentProps = toRaw(props)
|
||||||
const [options] = normalizePropsOptions(rawOptions)
|
const [options] = normalizePropsOptions(instance.type)
|
||||||
|
|
||||||
if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) {
|
if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) {
|
||||||
if (patchFlag & PatchFlags.PROPS) {
|
if (patchFlag & PatchFlags.PROPS) {
|
||||||
@ -211,8 +214,8 @@ export function updateProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__ && rawOptions && rawProps) {
|
if (__DEV__ && rawProps) {
|
||||||
validateProps(props, rawOptions)
|
validateProps(props, instance.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,9 +225,7 @@ function setFullProps(
|
|||||||
props: Data,
|
props: Data,
|
||||||
attrs: Data
|
attrs: Data
|
||||||
) {
|
) {
|
||||||
const [options, needCastKeys] = normalizePropsOptions(
|
const [options, needCastKeys] = normalizePropsOptions(instance.type)
|
||||||
instance.type.props
|
|
||||||
)
|
|
||||||
const emits = instance.type.emits
|
const emits = instance.type.emits
|
||||||
|
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
@ -292,16 +293,38 @@ function resolvePropValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizePropsOptions(
|
export function normalizePropsOptions(
|
||||||
raw: ComponentPropsOptions | undefined
|
comp: Component
|
||||||
): NormalizedPropsOptions | [] {
|
): NormalizedPropsOptions | [] {
|
||||||
if (!raw) {
|
if (comp.__props) {
|
||||||
return EMPTY_ARR as any
|
return comp.__props
|
||||||
}
|
|
||||||
if ((raw as any)._n) {
|
|
||||||
return (raw as any)._n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const raw = comp.props
|
||||||
const normalized: NormalizedPropsOptions[0] = {}
|
const normalized: NormalizedPropsOptions[0] = {}
|
||||||
const needCastKeys: NormalizedPropsOptions[1] = []
|
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)) {
|
if (isArray(raw)) {
|
||||||
for (let i = 0; i < raw.length; i++) {
|
for (let i = 0; i < raw.length; i++) {
|
||||||
if (__DEV__ && !isString(raw[i])) {
|
if (__DEV__ && !isString(raw[i])) {
|
||||||
@ -312,7 +335,7 @@ export function normalizePropsOptions(
|
|||||||
normalized[normalizedKey] = EMPTY_OBJ
|
normalized[normalizedKey] = EMPTY_OBJ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (raw) {
|
||||||
if (__DEV__ && !isObject(raw)) {
|
if (__DEV__ && !isObject(raw)) {
|
||||||
warn(`invalid props options`, raw)
|
warn(`invalid props options`, raw)
|
||||||
}
|
}
|
||||||
@ -337,7 +360,7 @@ export function normalizePropsOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys]
|
const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys]
|
||||||
def(raw, '_n', normalizedEntry)
|
comp.__props = normalizedEntry
|
||||||
return normalizedEntry
|
return normalizedEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,9 +391,12 @@ function getTypeIndex(
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
|
function validateProps(props: Data, comp: Component) {
|
||||||
const rawValues = toRaw(props)
|
const rawValues = toRaw(props)
|
||||||
const options = normalizePropsOptions(rawOptions)[0]
|
const options = normalizePropsOptions(comp)[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
|
||||||
@ -378,6 +404,9 @@ function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function validatePropName(key: string) {
|
function validatePropName(key: string) {
|
||||||
if (key[0] !== '$') {
|
if (key[0] !== '$') {
|
||||||
return true
|
return true
|
||||||
@ -387,6 +416,9 @@ function validatePropName(key: string) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function validateProp(
|
function validateProp(
|
||||||
name: string,
|
name: string,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
@ -434,6 +466,9 @@ type AssertionResult = {
|
|||||||
expectedType: string
|
expectedType: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
||||||
let valid
|
let valid
|
||||||
const expectedType = getType(type)
|
const expectedType = getType(type)
|
||||||
@ -457,6 +492,9 @@ function assertType(value: unknown, type: PropConstructor): AssertionResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function getInvalidTypeMessage(
|
function getInvalidTypeMessage(
|
||||||
name: string,
|
name: string,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
@ -485,6 +523,9 @@ function getInvalidTypeMessage(
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function styleValue(value: unknown, type: string): string {
|
function styleValue(value: unknown, type: string): string {
|
||||||
if (type === 'String') {
|
if (type === 'String') {
|
||||||
return `"${value}"`
|
return `"${value}"`
|
||||||
@ -495,11 +536,17 @@ function styleValue(value: unknown, type: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function isExplicable(type: string): boolean {
|
function isExplicable(type: string): boolean {
|
||||||
const explicitTypes = ['string', 'number', 'boolean']
|
const explicitTypes = ['string', 'number', 'boolean']
|
||||||
return explicitTypes.some(elem => type.toLowerCase() === elem)
|
return explicitTypes.some(elem => type.toLowerCase() === elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
function isBoolean(...args: string[]): boolean {
|
function isBoolean(...args: string[]): boolean {
|
||||||
return args.some(elem => elem.toLowerCase() === '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)
|
// only cache other properties when instance has declared (thus stable)
|
||||||
// props
|
// props
|
||||||
type.props &&
|
type.props &&
|
||||||
hasOwn(normalizePropsOptions(type.props)[0]!, key)
|
hasOwn(normalizePropsOptions(type)[0]!, key)
|
||||||
) {
|
) {
|
||||||
accessCache![key] = AccessTypes.PROPS
|
accessCache![key] = AccessTypes.PROPS
|
||||||
return props![key]
|
return props![key]
|
||||||
@ -347,7 +347,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)) ||
|
||||||
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
|
(type.props && hasOwn(normalizePropsOptions(type)[0]!, key)) ||
|
||||||
hasOwn(ctx, key) ||
|
hasOwn(ctx, key) ||
|
||||||
hasOwn(publicPropertiesMap, key) ||
|
hasOwn(publicPropertiesMap, key) ||
|
||||||
hasOwn(appContext.config.globalProperties, key)
|
hasOwn(appContext.config.globalProperties, key)
|
||||||
@ -430,12 +430,10 @@ export function createRenderContext(instance: ComponentInternalInstance) {
|
|||||||
export function exposePropsOnRenderContext(
|
export function exposePropsOnRenderContext(
|
||||||
instance: ComponentInternalInstance
|
instance: ComponentInternalInstance
|
||||||
) {
|
) {
|
||||||
const {
|
const { ctx, type } = instance
|
||||||
ctx,
|
const propsOptions = normalizePropsOptions(type)[0]
|
||||||
type: { props: propsOptions }
|
|
||||||
} = instance
|
|
||||||
if (propsOptions) {
|
if (propsOptions) {
|
||||||
Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
|
Object.keys(propsOptions).forEach(key => {
|
||||||
Object.defineProperty(ctx, key, {
|
Object.defineProperty(ctx, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user