mirror of https://github.com/vuejs/core.git
wip: props casting
This commit is contained in:
parent
0986051f12
commit
435ca32ff8
|
@ -9,23 +9,26 @@ import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
SetupContext,
|
SetupContext,
|
||||||
} from './component'
|
} 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 { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler'
|
||||||
import { insert } from './dom/element'
|
import { insert } from './dom/element'
|
||||||
import { normalizeContainer } from './apiRender'
|
import { normalizeContainer } from './apiRender'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
import { normalizePropsOptions, resolvePropValue } from './componentProps'
|
||||||
|
import type { Block } from './block'
|
||||||
|
|
||||||
interface RawProps {
|
interface RawProps {
|
||||||
[key: string]: any
|
[key: string]: PropSource
|
||||||
$?: DynamicPropsSource[]
|
$?: DynamicPropsSource[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicPropsSource = Record<string, any> | (() => Record<string, any>)
|
type PropSource<T = any> = T | (() => T)
|
||||||
|
|
||||||
|
type DynamicPropsSource = PropSource<Record<string, any>>
|
||||||
|
|
||||||
export function createComponentSimple(
|
export function createComponentSimple(
|
||||||
component: Component,
|
component: Component,
|
||||||
rawProps?: RawProps,
|
rawProps?: RawProps,
|
||||||
): any {
|
): Block {
|
||||||
const instance = new ComponentInstance(
|
const instance = new ComponentInstance(
|
||||||
component,
|
component,
|
||||||
rawProps,
|
rawProps,
|
||||||
|
@ -39,11 +42,10 @@ export function createComponentSimple(
|
||||||
const setupFn = isFunction(component) ? component : component.setup
|
const setupFn = isFunction(component) ? component : component.setup
|
||||||
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
|
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
|
||||||
const node = setupFn!(
|
const node = setupFn!(
|
||||||
// TODO __DEV__ ? shallowReadonly(props) :
|
|
||||||
instance.props,
|
instance.props,
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
setupContext,
|
setupContext,
|
||||||
)
|
) as Block
|
||||||
|
|
||||||
// single root, inherit attrs
|
// single root, inherit attrs
|
||||||
// let i
|
// let i
|
||||||
|
@ -74,48 +76,26 @@ export class ComponentInstance {
|
||||||
attrs: Record<string, any>
|
attrs: Record<string, any>
|
||||||
constructor(comp: Component, rawProps?: RawProps) {
|
constructor(comp: Component, rawProps?: RawProps) {
|
||||||
this.type = comp
|
this.type = comp
|
||||||
|
|
||||||
// init props
|
// init props
|
||||||
|
|
||||||
// TODO fast path for all static props
|
|
||||||
|
|
||||||
let mayHaveFallthroughAttrs = false
|
let mayHaveFallthroughAttrs = false
|
||||||
if (rawProps && comp.props) {
|
if (comp.props && rawProps && rawProps.$) {
|
||||||
if (rawProps.$) {
|
// has dynamic props, use proxy
|
||||||
// has dynamic props, use full proxy
|
const handlers = getDynamicPropsHandlers(comp, this)
|
||||||
const handlers = getPropsProxyHandlers(comp)
|
this.props = new Proxy(rawProps, handlers[0])
|
||||||
this.props = new Proxy(rawProps, handlers[0])
|
this.attrs = new Proxy(rawProps, handlers[1])
|
||||||
this.attrs = new Proxy(rawProps, handlers[1])
|
mayHaveFallthroughAttrs = true
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.props = EMPTY_OBJ
|
mayHaveFallthroughAttrs = initStaticProps(
|
||||||
this.attrs = rawProps || EMPTY_OBJ
|
comp,
|
||||||
mayHaveFallthroughAttrs = !!rawProps
|
rawProps,
|
||||||
|
(this.props = {}),
|
||||||
|
(this.attrs = {}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO validate props
|
||||||
|
|
||||||
if (mayHaveFallthroughAttrs) {
|
if (mayHaveFallthroughAttrs) {
|
||||||
// TODO apply fallthrough attrs
|
// 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
|
// 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
|
return isFunction(source) ? source() : source
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPropsProxyHandlers(
|
function getDynamicPropsHandlers(
|
||||||
comp: Component,
|
comp: Component,
|
||||||
|
instance: ComponentInstance,
|
||||||
): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
|
): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
|
||||||
if (comp.__propsHandlers) {
|
if (comp.__propsHandlers) {
|
||||||
return comp.__propsHandlers
|
return comp.__propsHandlers
|
||||||
}
|
}
|
||||||
let normalizedKeys: string[] | undefined
|
let normalizedKeys: string[] | undefined
|
||||||
const normalizedOptions = normalizePropsOptions(comp)[0]!
|
const propsOptions = normalizePropsOptions(comp)[0]!
|
||||||
const isProp = (key: string | symbol) => hasOwn(normalizedOptions, key)
|
const isProp = (key: string | symbol) => hasOwn(propsOptions, key)
|
||||||
|
|
||||||
const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => {
|
const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => {
|
||||||
if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) {
|
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.
|
// TODO default value, casting, etc.
|
||||||
return target[key]
|
return castProp(resolveSource(target[key as string]))
|
||||||
}
|
}
|
||||||
if (target.$) {
|
if (target.$) {
|
||||||
let source, resolved
|
let source, resolved
|
||||||
for (source of target.$) {
|
for (source of target.$) {
|
||||||
resolved = resolveSource(source)
|
resolved = resolveSource(source)
|
||||||
if (hasOwn(resolved, key)) {
|
if (hasOwn(resolved, key)) {
|
||||||
return resolved[key]
|
return castProp(resolved[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return castProp(undefined, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,10 +208,9 @@ function getPropsProxyHandlers(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ownKeys: () =>
|
ownKeys: () =>
|
||||||
normalizedKeys || (normalizedKeys = Object.keys(normalizedOptions)),
|
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
|
||||||
set: NO,
|
set: NO,
|
||||||
deleteProperty: NO,
|
deleteProperty: NO,
|
||||||
// TODO dev traps to prevent mutation
|
|
||||||
} satisfies ProxyHandler<RawProps>
|
} satisfies ProxyHandler<RawProps>
|
||||||
|
|
||||||
const hasAttr = (target: RawProps, key: string | symbol) => {
|
const hasAttr = (target: RawProps, key: string | symbol) => {
|
||||||
|
|
|
@ -11,11 +11,7 @@ import {
|
||||||
import type { Data } from '@vue/runtime-shared'
|
import type { Data } from '@vue/runtime-shared'
|
||||||
import { shallowReactive } from '@vue/reactivity'
|
import { shallowReactive } from '@vue/reactivity'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import {
|
import type { Component, ComponentInternalInstance } from './component'
|
||||||
type Component,
|
|
||||||
type ComponentInternalInstance,
|
|
||||||
setCurrentInstance,
|
|
||||||
} from './component'
|
|
||||||
import { patchAttrs } from './componentAttrs'
|
import { patchAttrs } from './componentAttrs'
|
||||||
import { firstEffect } from './renderEffect'
|
import { firstEffect } from './renderEffect'
|
||||||
|
|
||||||
|
@ -144,7 +140,7 @@ function registerProp(
|
||||||
const [options, needCastKeys] = instance.propsOptions
|
const [options, needCastKeys] = instance.propsOptions
|
||||||
const needCast = needCastKeys && needCastKeys.includes(key)
|
const needCast = needCastKeys && needCastKeys.includes(key)
|
||||||
const withCast = (value: unknown, absent?: boolean) =>
|
const withCast = (value: unknown, absent?: boolean) =>
|
||||||
resolvePropValue(options!, props, key, value, instance, absent)
|
resolvePropValue(options!, props, key, value, absent)
|
||||||
|
|
||||||
if (isAbsent) {
|
if (isAbsent) {
|
||||||
props[key] = needCast ? withCast(undefined, true) : undefined
|
props[key] = needCast ? withCast(undefined, true) : undefined
|
||||||
|
@ -209,14 +205,13 @@ export function getDynamicPropValue(
|
||||||
return [undefined, true]
|
return [undefined, true]
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePropValue(
|
export function resolvePropValue(
|
||||||
options: NormalizedProps,
|
options: NormalizedProps,
|
||||||
props: Data,
|
props: Data,
|
||||||
key: string,
|
key: string,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
isAbsent?: boolean,
|
isAbsent?: boolean,
|
||||||
) {
|
): unknown {
|
||||||
const opt = options[key]
|
const opt = options[key]
|
||||||
if (opt != null) {
|
if (opt != null) {
|
||||||
const hasDefault = hasOwn(opt, 'default')
|
const hasDefault = hasOwn(opt, 'default')
|
||||||
|
@ -228,15 +223,7 @@ function resolvePropValue(
|
||||||
!opt.skipFactory &&
|
!opt.skipFactory &&
|
||||||
isFunction(defaultValue)
|
isFunction(defaultValue)
|
||||||
) {
|
) {
|
||||||
// TODO: caching?
|
value = defaultValue.call(null, props)
|
||||||
// const { propsDefaults } = instance
|
|
||||||
// if (key in propsDefaults) {
|
|
||||||
// value = propsDefaults[key]
|
|
||||||
// } else {
|
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
instance.scope.run(() => (value = defaultValue.call(null, props)))
|
|
||||||
reset()
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
value = defaultValue
|
value = defaultValue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue