]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: props casting
authorEvan You <evan@vuejs.org>
Mon, 2 Dec 2024 15:51:48 +0000 (23:51 +0800)
committerEvan You <evan@vuejs.org>
Mon, 2 Dec 2024 15:52:04 +0000 (23:52 +0800)
packages/runtime-vapor/src/apiCreateComponentSimple.ts
packages/runtime-vapor/src/componentProps.ts

index c03873402b46a5a77d57f006c8eeaa7b475bb4d9..edcd778759e6bc2c22533214f21626e553799863 100644 (file)
@@ -9,23 +9,26 @@ import {
   type ComponentInternalInstance,
   SetupContext,
 } from './component'
-import { EMPTY_OBJ, NO, hasOwn, isFunction } from '@vue/shared'
+import { NO, camelize, hasOwn, isFunction } from '@vue/shared'
 import { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler'
 import { insert } from './dom/element'
 import { normalizeContainer } from './apiRender'
-import { normalizePropsOptions } from './componentProps'
+import { normalizePropsOptions, resolvePropValue } from './componentProps'
+import type { Block } from './block'
 
 interface RawProps {
-  [key: string]: any
+  [key: string]: PropSource
   $?: DynamicPropsSource[]
 }
 
-type DynamicPropsSource = Record<string, any> | (() => Record<string, any>)
+type PropSource<T = any> = T | (() => T)
+
+type DynamicPropsSource = PropSource<Record<string, any>>
 
 export function createComponentSimple(
   component: Component,
   rawProps?: RawProps,
-): any {
+): Block {
   const instance = new ComponentInstance(
     component,
     rawProps,
@@ -39,11 +42,10 @@ export function createComponentSimple(
   const setupFn = isFunction(component) ? component : component.setup
   const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
   const node = setupFn!(
-    // TODO __DEV__ ? shallowReadonly(props) :
     instance.props,
     // @ts-expect-error
     setupContext,
-  )
+  ) as Block
 
   // single root, inherit attrs
   // let i
@@ -74,48 +76,26 @@ export class ComponentInstance {
   attrs: Record<string, any>
   constructor(comp: Component, rawProps?: RawProps) {
     this.type = comp
-    // init props
-
-    // TODO fast path for all static props
 
+    // init props
     let mayHaveFallthroughAttrs = false
-    if (rawProps && comp.props) {
-      if (rawProps.$) {
-        // has dynamic props, use full proxy
-        const handlers = getPropsProxyHandlers(comp)
-        this.props = new Proxy(rawProps, handlers[0])
-        this.attrs = new Proxy(rawProps, handlers[1])
-        mayHaveFallthroughAttrs = true
-      } else {
-        // fast path for all static prop keys
-        this.props = rawProps
-        this.attrs = {}
-        const propsOptions = normalizePropsOptions(comp)[0]!
-        for (const key in propsOptions) {
-          if (!(key in rawProps)) {
-            rawProps[key] = undefined // TODO default value / casting
-          } else {
-            // TODO override getter with default value / casting
-          }
-        }
-        for (const key in rawProps) {
-          if (!(key in propsOptions)) {
-            Object.defineProperty(
-              this.attrs,
-              key,
-              Object.getOwnPropertyDescriptor(rawProps, key)!,
-            )
-            delete rawProps[key]
-            mayHaveFallthroughAttrs = true
-          }
-        }
-      }
+    if (comp.props && rawProps && rawProps.$) {
+      // has dynamic props, use proxy
+      const handlers = getDynamicPropsHandlers(comp, this)
+      this.props = new Proxy(rawProps, handlers[0])
+      this.attrs = new Proxy(rawProps, handlers[1])
+      mayHaveFallthroughAttrs = true
     } else {
-      this.props = EMPTY_OBJ
-      this.attrs = rawProps || EMPTY_OBJ
-      mayHaveFallthroughAttrs = !!rawProps
+      mayHaveFallthroughAttrs = initStaticProps(
+        comp,
+        rawProps,
+        (this.props = {}),
+        (this.attrs = {}),
+      )
     }
 
+    // TODO validate props
+
     if (mayHaveFallthroughAttrs) {
       // TODO apply fallthrough attrs
     }
@@ -123,36 +103,95 @@ export class ComponentInstance {
   }
 }
 
+function initStaticProps(
+  comp: Component,
+  rawProps: RawProps | undefined,
+  props: any,
+  attrs: any,
+): boolean {
+  let hasAttrs = false
+  const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
+  for (const key in rawProps) {
+    const normalizedKey = camelize(key)
+    const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
+    const source = rawProps[key]
+    if (propsOptions && normalizedKey in propsOptions) {
+      if (isFunction(source)) {
+        Object.defineProperty(props, normalizedKey, {
+          enumerable: true,
+          get: needCast
+            ? () =>
+                resolvePropValue(propsOptions, props, normalizedKey, source())
+            : source,
+        })
+      } else {
+        props[normalizedKey] = needCast
+          ? resolvePropValue(propsOptions, props, normalizedKey, source)
+          : source
+      }
+    } else {
+      if (isFunction(source)) {
+        Object.defineProperty(attrs, key, {
+          enumerable: true,
+          get: source,
+        })
+      } else {
+        attrs[normalizedKey] = source
+      }
+      hasAttrs = true
+    }
+  }
+  for (const key in propsOptions) {
+    if (!(key in props)) {
+      props[key] = resolvePropValue(propsOptions, props, key, undefined, true)
+    }
+  }
+  return hasAttrs
+}
+
 // TODO optimization: maybe convert functions into computeds
-function resolveSource(source: DynamicPropsSource): Record<string, any> {
+function resolveSource(source: PropSource): Record<string, any> {
   return isFunction(source) ? source() : source
 }
 
-function getPropsProxyHandlers(
+function getDynamicPropsHandlers(
   comp: Component,
+  instance: ComponentInstance,
 ): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
   if (comp.__propsHandlers) {
     return comp.__propsHandlers
   }
   let normalizedKeys: string[] | undefined
-  const normalizedOptions = normalizePropsOptions(comp)[0]!
-  const isProp = (key: string | symbol) => hasOwn(normalizedOptions, key)
+  const propsOptions = normalizePropsOptions(comp)[0]!
+  const isProp = (key: string | symbol) => hasOwn(propsOptions, key)
 
   const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => {
     if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) {
-      if (hasOwn(target, key)) {
+      const castProp = (value: any, isAbsent?: boolean) =>
+        asProp
+          ? resolvePropValue(
+              propsOptions,
+              instance.props,
+              key as string,
+              value,
+              isAbsent,
+            )
+          : value
+
+      if (key in target) {
         // TODO default value, casting, etc.
-        return target[key]
+        return castProp(resolveSource(target[key as string]))
       }
       if (target.$) {
         let source, resolved
         for (source of target.$) {
           resolved = resolveSource(source)
           if (hasOwn(resolved, key)) {
-            return resolved[key]
+            return castProp(resolved[key])
           }
         }
       }
+      return castProp(undefined, true)
     }
   }
 
@@ -169,10 +208,9 @@ function getPropsProxyHandlers(
       }
     },
     ownKeys: () =>
-      normalizedKeys || (normalizedKeys = Object.keys(normalizedOptions)),
+      normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
     set: NO,
     deleteProperty: NO,
-    // TODO dev traps to prevent mutation
   } satisfies ProxyHandler<RawProps>
 
   const hasAttr = (target: RawProps, key: string | symbol) => {
index e5918865542ee1a5827f8b913d860072db9524fc..d04e3d576c653e6ec28138c4b342c23fe6f3e6c3 100644 (file)
@@ -11,11 +11,7 @@ import {
 import type { Data } from '@vue/runtime-shared'
 import { shallowReactive } from '@vue/reactivity'
 import { warn } from './warning'
-import {
-  type Component,
-  type ComponentInternalInstance,
-  setCurrentInstance,
-} from './component'
+import type { Component, ComponentInternalInstance } from './component'
 import { patchAttrs } from './componentAttrs'
 import { firstEffect } from './renderEffect'
 
@@ -144,7 +140,7 @@ function registerProp(
   const [options, needCastKeys] = instance.propsOptions
   const needCast = needCastKeys && needCastKeys.includes(key)
   const withCast = (value: unknown, absent?: boolean) =>
-    resolvePropValue(options!, props, key, value, instance, absent)
+    resolvePropValue(options!, props, key, value, absent)
 
   if (isAbsent) {
     props[key] = needCast ? withCast(undefined, true) : undefined
@@ -209,14 +205,13 @@ export function getDynamicPropValue(
   return [undefined, true]
 }
 
-function resolvePropValue(
+export function resolvePropValue(
   options: NormalizedProps,
   props: Data,
   key: string,
   value: unknown,
-  instance: ComponentInternalInstance,
   isAbsent?: boolean,
-) {
+): unknown {
   const opt = options[key]
   if (opt != null) {
     const hasDefault = hasOwn(opt, 'default')
@@ -228,15 +223,7 @@ function resolvePropValue(
         !opt.skipFactory &&
         isFunction(defaultValue)
       ) {
-        // TODO: caching?
-        // const { propsDefaults } = instance
-        // if (key in propsDefaults) {
-        //   value = propsDefaults[key]
-        // } else {
-        const reset = setCurrentInstance(instance)
-        instance.scope.run(() => (value = defaultValue.call(null, props)))
-        reset()
-        // }
+        value = defaultValue.call(null, props)
       } else {
         value = defaultValue
       }