Просмотр исходного кода

wip: vapor component props validation

Evan You 1 год назад
Родитель
Сommit
93a16af08e

+ 18 - 18
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<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 + '".')
   }
 }
 

+ 1 - 0
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'

+ 6 - 19
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
   }
 }

+ 55 - 1
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<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()
+  })
+}