From: Evan You Date: Mon, 2 Dec 2024 15:51:48 +0000 (+0800) Subject: wip: props casting X-Git-Tag: v3.6.0-alpha.1~16^2~254 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=435ca32ff83b940f93cd268cc27a3e70aafae856;p=thirdparty%2Fvuejs%2Fcore.git wip: props casting --- diff --git a/packages/runtime-vapor/src/apiCreateComponentSimple.ts b/packages/runtime-vapor/src/apiCreateComponentSimple.ts index c03873402b..edcd778759 100644 --- a/packages/runtime-vapor/src/apiCreateComponentSimple.ts +++ b/packages/runtime-vapor/src/apiCreateComponentSimple.ts @@ -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 | (() => Record) +type PropSource = T | (() => T) + +type DynamicPropsSource = PropSource> 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 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 { +function resolveSource(source: PropSource): Record { return isFunction(source) ? source() : source } -function getPropsProxyHandlers( +function getDynamicPropsHandlers( comp: Component, + instance: ComponentInstance, ): [ProxyHandler, ProxyHandler] { 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 const hasAttr = (target: RawProps, key: string | symbol) => { diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index e591886554..d04e3d576c 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -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 }