]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: vapor component props validation
authorEvan You <evan@vuejs.org>
Thu, 5 Dec 2024 08:14:24 +0000 (16:14 +0800)
committerEvan You <evan@vuejs.org>
Thu, 5 Dec 2024 08:14:38 +0000 (16:14 +0800)
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentProps.ts

index d4069d3526a72ba5e30c29de93dd6865fd820df4..5a5808713e715c786d5f7e58f2fcf154f73fa6f1 100644 (file)
@@ -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<any> | 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 + '".')
   }
 }
 
index f897de253ca79c288eec95cd96e9c4accc014d19..589cdaac28540256bc80d1b3805a6b35e26d8115 100644 (file)
@@ -491,6 +491,7 @@ export {
   type NormalizedPropsOptions,
   baseNormalizePropsOptions,
   resolvePropValue,
+  validateProps,
 } from './componentProps'
 export { baseEmit, isEmitListener } from './componentEmits'
 export { type SchedulerJob, queueJob } from './scheduler'
index 0eb7ce228f8453f9f4d4ded382e5bd7e99b81cdd..614054a3b25b382d2eee8e7dcfcc99f6ede98fbd 100644 (file)
@@ -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
   }
 }
index 922e75a877cf0d0d72ab221c267abe8d910bf7c7..553975945a8153e320d6b96c76bdacbbc80cceb6 100644 (file)
@@ -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<string, () => 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()
+  })
+}