mirror of https://github.com/vuejs/core.git
wip: vapor component props validation
This commit is contained in:
parent
8725954244
commit
93a16af08e
|
@ -217,7 +217,7 @@ export function initProps(
|
||||||
|
|
||||||
// validation
|
// validation
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validateProps(rawProps || {}, props, instance)
|
validateProps(rawProps || {}, props, instance.propsOptions[0]!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStateful) {
|
if (isStateful) {
|
||||||
|
@ -371,7 +371,7 @@ export function updateProps(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validateProps(rawProps || {}, props, instance)
|
validateProps(rawProps || {}, props, instance.propsOptions[0]!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,23 +691,23 @@ function getType(ctor: Prop<any> | null): string {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev only
|
* dev only
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
function validateProps(
|
export function validateProps(
|
||||||
rawProps: Data,
|
rawProps: Data,
|
||||||
props: Data,
|
resolvedProps: Data,
|
||||||
instance: ComponentInternalInstance,
|
options: NormalizedProps,
|
||||||
) {
|
): void {
|
||||||
const resolvedValues = toRaw(props)
|
resolvedProps = toRaw(resolvedProps)
|
||||||
const options = instance.propsOptions[0]
|
|
||||||
const camelizePropsKey = Object.keys(rawProps).map(key => camelize(key))
|
const camelizePropsKey = Object.keys(rawProps).map(key => camelize(key))
|
||||||
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
|
||||||
validateProp(
|
validateProp(
|
||||||
key,
|
key,
|
||||||
resolvedValues[key],
|
resolvedProps[key],
|
||||||
opt,
|
opt,
|
||||||
__DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
|
__DEV__ ? shallowReadonly(resolvedProps) : resolvedProps,
|
||||||
!camelizePropsKey.includes(key),
|
!camelizePropsKey.includes(key),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -717,16 +717,16 @@ function validateProps(
|
||||||
* dev only
|
* dev only
|
||||||
*/
|
*/
|
||||||
function validateProp(
|
function validateProp(
|
||||||
name: string,
|
key: string,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
prop: PropOptions,
|
propOptions: PropOptions,
|
||||||
props: Data,
|
resolvedProps: Data,
|
||||||
isAbsent: boolean,
|
isAbsent: boolean,
|
||||||
) {
|
) {
|
||||||
const { type, required, validator, skipCheck } = prop
|
const { type, required, validator, skipCheck } = propOptions
|
||||||
// required!
|
// required!
|
||||||
if (required && isAbsent) {
|
if (required && isAbsent) {
|
||||||
warn('Missing required prop: "' + name + '"')
|
warn('Missing required prop: "' + key + '"')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// missing but optional
|
// missing but optional
|
||||||
|
@ -745,13 +745,13 @@ function validateProp(
|
||||||
isValid = valid
|
isValid = valid
|
||||||
}
|
}
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
warn(getInvalidTypeMessage(name, value, expectedTypes))
|
warn(getInvalidTypeMessage(key, value, expectedTypes))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// custom validator
|
// custom validator
|
||||||
if (validator && !validator(value, props)) {
|
if (validator && !validator(value, resolvedProps)) {
|
||||||
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
|
warn('Invalid prop: custom validator check failed for prop "' + key + '".')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -491,6 +491,7 @@ export {
|
||||||
type NormalizedPropsOptions,
|
type NormalizedPropsOptions,
|
||||||
baseNormalizePropsOptions,
|
baseNormalizePropsOptions,
|
||||||
resolvePropValue,
|
resolvePropValue,
|
||||||
|
validateProps,
|
||||||
} from './componentProps'
|
} from './componentProps'
|
||||||
export { baseEmit, isEmitListener } from './componentEmits'
|
export { baseEmit, isEmitListener } from './componentEmits'
|
||||||
export { type SchedulerJob, queueJob } from './scheduler'
|
export { type SchedulerJob, queueJob } from './scheduler'
|
||||||
|
|
|
@ -16,11 +16,13 @@ import {
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { type Block, isBlock } from './block'
|
import { type Block, isBlock } from './block'
|
||||||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||||
import { EMPTY_OBJ, hasOwn, isFunction } from '@vue/shared'
|
import { EMPTY_OBJ, isFunction } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type RawProps,
|
type RawProps,
|
||||||
getPropsProxyHandlers,
|
getPropsProxyHandlers,
|
||||||
|
hasFallthroughAttrs,
|
||||||
normalizePropsOptions,
|
normalizePropsOptions,
|
||||||
|
setupPropsValidation,
|
||||||
} from './componentProps'
|
} from './componentProps'
|
||||||
import { setDynamicProp } from './dom/prop'
|
import { setDynamicProp } from './dom/prop'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
@ -208,31 +210,16 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
const handlers = getPropsProxyHandlers(comp, this)
|
const handlers = getPropsProxyHandlers(comp, this)
|
||||||
this.props = comp.props ? new Proxy(target, handlers[0]!) : {}
|
this.props = comp.props ? new Proxy(target, handlers[0]!) : {}
|
||||||
this.attrs = new Proxy(target, handlers[1])
|
this.attrs = new Proxy(target, handlers[1])
|
||||||
|
this.hasFallthrough = hasFallthroughAttrs(comp, rawProps)
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
// validate props
|
||||||
|
if (rawProps) setupPropsValidation(this)
|
||||||
// cache normalized options for dev only emit check
|
// cache normalized options for dev only emit check
|
||||||
this.propsOptions = normalizePropsOptions(comp)
|
this.propsOptions = normalizePropsOptions(comp)
|
||||||
this.emitsOptions = normalizeEmitsOptions(comp)
|
this.emitsOptions = normalizeEmitsOptions(comp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine fallthrough
|
|
||||||
this.hasFallthrough = false
|
|
||||||
if (rawProps) {
|
|
||||||
if (rawProps.$ || !comp.props) {
|
|
||||||
this.hasFallthrough = true
|
|
||||||
} else {
|
|
||||||
// check if rawProps contains any keys not declared
|
|
||||||
const propsOptions = normalizePropsOptions(comp)[0]
|
|
||||||
for (const key in rawProps) {
|
|
||||||
if (!hasOwn(propsOptions!, key)) {
|
|
||||||
this.hasFallthrough = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO validate props
|
|
||||||
// TODO init slots
|
// TODO init slots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import { EMPTY_ARR, NO, YES, hasOwn, isFunction } from '@vue/shared'
|
import { EMPTY_ARR, NO, YES, extend, hasOwn, isFunction } from '@vue/shared'
|
||||||
import type { VaporComponent, VaporComponentInstance } from './component'
|
import type { VaporComponent, VaporComponentInstance } from './component'
|
||||||
import {
|
import {
|
||||||
type NormalizedPropsOptions,
|
type NormalizedPropsOptions,
|
||||||
baseNormalizePropsOptions,
|
baseNormalizePropsOptions,
|
||||||
isEmitListener,
|
isEmitListener,
|
||||||
|
popWarningContext,
|
||||||
|
pushWarningContext,
|
||||||
resolvePropValue,
|
resolvePropValue,
|
||||||
|
validateProps,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { normalizeEmitsOptions } from './componentEmits'
|
import { normalizeEmitsOptions } from './componentEmits'
|
||||||
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
export type RawProps = Record<string, () => unknown> & {
|
export type RawProps = Record<string, () => unknown> & {
|
||||||
$?: DynamicPropsSource[]
|
$?: DynamicPropsSource[]
|
||||||
|
@ -174,3 +178,53 @@ function resolveDefault(
|
||||||
) {
|
) {
|
||||||
return factory.call(null, instance.props)
|
return factory.call(null, instance.props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasFallthroughAttrs(
|
||||||
|
comp: VaporComponent,
|
||||||
|
rawProps: RawProps | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (rawProps) {
|
||||||
|
// determine fallthrough
|
||||||
|
if (rawProps.$ || !comp.props) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// check if rawProps contains any keys not declared
|
||||||
|
const propsOptions = normalizePropsOptions(comp)[0]
|
||||||
|
for (const key in rawProps) {
|
||||||
|
if (!hasOwn(propsOptions!, key)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev only
|
||||||
|
*/
|
||||||
|
export function setupPropsValidation(instance: VaporComponentInstance): void {
|
||||||
|
const rawProps = instance.rawProps
|
||||||
|
if (!rawProps) return
|
||||||
|
renderEffect(() => {
|
||||||
|
const mergedRawProps = extend({}, rawProps)
|
||||||
|
if (rawProps.$) {
|
||||||
|
for (const source of rawProps.$) {
|
||||||
|
const isDynamic = isFunction(source)
|
||||||
|
const resolved = isDynamic ? source() : source
|
||||||
|
for (const key in resolved) {
|
||||||
|
mergedRawProps[key] = isDynamic
|
||||||
|
? resolved[key]
|
||||||
|
: (resolved[key] as Function)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushWarningContext(instance)
|
||||||
|
validateProps(
|
||||||
|
mergedRawProps,
|
||||||
|
instance.props,
|
||||||
|
normalizePropsOptions(instance.type)[0]!,
|
||||||
|
)
|
||||||
|
popWarningContext()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue