From: Evan You Date: Thu, 5 Dec 2024 08:14:24 +0000 (+0800) Subject: wip: vapor component props validation X-Git-Tag: v3.6.0-alpha.1~16^2~233 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=93a16af08e478cc7f5492286a046e3dc7c735e5d;p=thirdparty%2Fvuejs%2Fcore.git wip: vapor component props validation --- diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index d4069d3526..5a5808713e 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -217,7 +217,7 @@ export function initProps( // validation if (__DEV__) { - validateProps(rawProps || {}, props, instance) + validateProps(rawProps || {}, props, instance.propsOptions[0]!) } if (isStateful) { @@ -371,7 +371,7 @@ export function updateProps( } if (__DEV__) { - validateProps(rawProps || {}, props, instance) + validateProps(rawProps || {}, props, instance.propsOptions[0]!) } } @@ -691,23 +691,23 @@ function getType(ctor: Prop | null): string { /** * dev only + * @internal */ -function validateProps( +export function validateProps( rawProps: Data, - props: Data, - instance: ComponentInternalInstance, -) { - const resolvedValues = toRaw(props) - const options = instance.propsOptions[0] + resolvedProps: Data, + options: NormalizedProps, +): void { + resolvedProps = toRaw(resolvedProps) const camelizePropsKey = Object.keys(rawProps).map(key => camelize(key)) for (const key in options) { let opt = options[key] if (opt == null) continue validateProp( key, - resolvedValues[key], + resolvedProps[key], opt, - __DEV__ ? shallowReadonly(resolvedValues) : resolvedValues, + __DEV__ ? shallowReadonly(resolvedProps) : resolvedProps, !camelizePropsKey.includes(key), ) } @@ -717,16 +717,16 @@ function validateProps( * dev only */ function validateProp( - name: string, + key: string, value: unknown, - prop: PropOptions, - props: Data, + propOptions: PropOptions, + resolvedProps: Data, isAbsent: boolean, ) { - const { type, required, validator, skipCheck } = prop + const { type, required, validator, skipCheck } = propOptions // required! if (required && isAbsent) { - warn('Missing required prop: "' + name + '"') + warn('Missing required prop: "' + key + '"') return } // missing but optional @@ -745,13 +745,13 @@ function validateProp( isValid = valid } if (!isValid) { - warn(getInvalidTypeMessage(name, value, expectedTypes)) + warn(getInvalidTypeMessage(key, value, expectedTypes)) return } } // custom validator - if (validator && !validator(value, props)) { - warn('Invalid prop: custom validator check failed for prop "' + name + '".') + if (validator && !validator(value, resolvedProps)) { + warn('Invalid prop: custom validator check failed for prop "' + key + '".') } } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index f897de253c..589cdaac28 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -491,6 +491,7 @@ export { type NormalizedPropsOptions, baseNormalizePropsOptions, resolvePropValue, + validateProps, } from './componentProps' export { baseEmit, isEmitListener } from './componentEmits' export { type SchedulerJob, queueJob } from './scheduler' diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 0eb7ce228f..614054a3b2 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -16,11 +16,13 @@ import { } from '@vue/runtime-dom' import { type Block, isBlock } from './block' import { pauseTracking, resetTracking } from '@vue/reactivity' -import { EMPTY_OBJ, hasOwn, isFunction } from '@vue/shared' +import { EMPTY_OBJ, isFunction } from '@vue/shared' import { type RawProps, getPropsProxyHandlers, + hasFallthroughAttrs, normalizePropsOptions, + setupPropsValidation, } from './componentProps' import { setDynamicProp } from './dom/prop' import { renderEffect } from './renderEffect' @@ -208,31 +210,16 @@ export class VaporComponentInstance implements GenericComponentInstance { const handlers = getPropsProxyHandlers(comp, this) this.props = comp.props ? new Proxy(target, handlers[0]!) : {} this.attrs = new Proxy(target, handlers[1]) + this.hasFallthrough = hasFallthroughAttrs(comp, rawProps) if (__DEV__) { + // validate props + if (rawProps) setupPropsValidation(this) // cache normalized options for dev only emit check this.propsOptions = normalizePropsOptions(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 } } diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 922e75a877..553975945a 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -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 NormalizedPropsOptions, baseNormalizePropsOptions, isEmitListener, + popWarningContext, + pushWarningContext, resolvePropValue, + validateProps, } from '@vue/runtime-dom' import { normalizeEmitsOptions } from './componentEmits' +import { renderEffect } from './renderEffect' export type RawProps = Record unknown> & { $?: DynamicPropsSource[] @@ -174,3 +178,53 @@ function resolveDefault( ) { 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() + }) +}