wip: slots
This commit is contained in:
parent
e0a66d0381
commit
c0c06813a7
@ -7,19 +7,12 @@ import {
|
||||
} from '@vue/observer'
|
||||
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
||||
import { RenderProxyHandlers } from './componentProxy'
|
||||
import { ComponentPropsOptions, PropValidator } from './componentProps'
|
||||
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
|
||||
import { Slots } from './componentSlots'
|
||||
|
||||
export type Data = { [key: string]: any }
|
||||
|
||||
type ExtractPropTypes<PropOptions> = {
|
||||
readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
|
||||
infer V
|
||||
>
|
||||
? V
|
||||
: PropOptions[key] extends null | undefined ? any : PropOptions[key]
|
||||
}
|
||||
|
||||
export type ComponentPublicProperties<P = Data, S = Data> = {
|
||||
$state: S
|
||||
$props: P
|
||||
@ -70,12 +63,6 @@ export interface LifecycleHooks {
|
||||
ec: LifecycleHook // errorCaptured
|
||||
}
|
||||
|
||||
export type Slot = (...args: any[]) => VNode[]
|
||||
|
||||
export type Slots = Readonly<{
|
||||
[name: string]: Slot
|
||||
}>
|
||||
|
||||
export type ComponentInstance<P = Data, S = Data> = {
|
||||
type: FunctionalComponent | ComponentOptions
|
||||
vnode: VNode
|
||||
|
@ -16,17 +16,25 @@ export type ComponentPropsOptions<P = Data> = {
|
||||
[K in keyof P]: PropValidator<P[K]>
|
||||
}
|
||||
|
||||
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
|
||||
type Prop<T> = { (): T } | { new (...args: any[]): T & object }
|
||||
|
||||
export type PropType<T> = Prop<T> | Prop<T>[]
|
||||
type PropType<T> = Prop<T> | Prop<T>[]
|
||||
|
||||
export type PropValidator<T> = PropOptions<T> | PropType<T>
|
||||
type PropValidator<T> = PropOptions<T> | PropType<T>
|
||||
|
||||
export interface PropOptions<T = any> {
|
||||
interface PropOptions<T = any> {
|
||||
type?: PropType<T> | true | null
|
||||
required?: boolean
|
||||
default?: T | null | undefined | (() => T | null | undefined)
|
||||
validator?(value: T): boolean
|
||||
validator?(value: any): boolean
|
||||
}
|
||||
|
||||
export type ExtractPropTypes<PropOptions> = {
|
||||
readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
|
||||
infer V
|
||||
>
|
||||
? V
|
||||
: PropOptions[key] extends null | true ? any : PropOptions[key]
|
||||
}
|
||||
|
||||
const enum BooleanFlags {
|
||||
|
59
packages/runtime-core/src/componentSlots.ts
Normal file
59
packages/runtime-core/src/componentSlots.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { ComponentInstance } from './component'
|
||||
import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
|
||||
import { isArray, isObject, isFunction } from '@vue/shared'
|
||||
|
||||
export type Slot = (...args: any[]) => VNode[]
|
||||
export type Slots = Readonly<{
|
||||
[name: string]: Slot
|
||||
}>
|
||||
export type RawSlots = {
|
||||
[name: string]: unknown
|
||||
}
|
||||
|
||||
const normalizeSlotValue = (value: unknown): VNode[] =>
|
||||
isArray(value)
|
||||
? value.map(normalizeVNode)
|
||||
: [normalizeVNode(value as VNodeChild)]
|
||||
|
||||
const normalizeSlot = (rawSlot: Function): Slot => (props: any) =>
|
||||
normalizeSlotValue(rawSlot(props))
|
||||
|
||||
export function resolveSlots(
|
||||
instance: ComponentInstance,
|
||||
children: NormalizedChildren
|
||||
) {
|
||||
let slots: Slots | void
|
||||
if (isObject(children) && !isArray(children)) {
|
||||
// pre-normalized slots object generated by compiler
|
||||
if ((children as any)._normalized) {
|
||||
slots = children as Slots
|
||||
} else {
|
||||
slots = {}
|
||||
for (const key in children) {
|
||||
let value = children[key]
|
||||
if (isFunction(value)) {
|
||||
;(slots as any)[key] = normalizeSlot(value)
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
// TODO show tip on using functions
|
||||
console.log('use function slots!')
|
||||
}
|
||||
value = normalizeSlotValue(value)
|
||||
;(slots as any)[key] = () => value
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (children != null) {
|
||||
// Array, string or null.
|
||||
// non object children passed to a component
|
||||
if (__DEV__) {
|
||||
// TODO show tip on using functions
|
||||
console.log('use function slots!')
|
||||
}
|
||||
const normalized = normalizeSlotValue(children)
|
||||
slots = { default: () => normalized }
|
||||
}
|
||||
if (slots !== void 0) {
|
||||
instance.slots = slots
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import {
|
||||
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
||||
import { resolveProps } from './componentProps'
|
||||
import { resolveSlots } from './componentSlots'
|
||||
|
||||
const prodEffectOptions = {
|
||||
scheduler: queueJob
|
||||
@ -201,7 +202,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
if (isString(vnode.children)) {
|
||||
hostSetElementText(el, vnode.children)
|
||||
} else if (vnode.children != null) {
|
||||
} else if (isArray(vnode.children)) {
|
||||
mountChildren(vnode.children, el)
|
||||
}
|
||||
hostInsert(el, container, anchor)
|
||||
@ -382,7 +383,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
if (target != null) {
|
||||
if (isString(children)) {
|
||||
hostSetElementText(target, children)
|
||||
} else if (children != null) {
|
||||
} else if (isArray(children)) {
|
||||
mountChildren(children, target)
|
||||
}
|
||||
} else {
|
||||
@ -407,7 +408,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
if (isString(children)) {
|
||||
hostSetElementText(target, '')
|
||||
hostSetElementText(nextTarget, children)
|
||||
} else if (children != null) {
|
||||
} else if (isArray(children)) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
move(children[i] as VNode, nextTarget, null)
|
||||
}
|
||||
@ -454,6 +455,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
// initial mount
|
||||
instance.vnode = vnode
|
||||
resolveProps(instance, vnode.props, Component.props)
|
||||
resolveSlots(instance, vnode.children)
|
||||
// setup stateful
|
||||
if (typeof Component === 'object') {
|
||||
setupStatefulComponent(instance)
|
||||
@ -479,7 +481,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
instance.vnode = next
|
||||
instance.next = null
|
||||
resolveProps(instance, next.props, Component.props)
|
||||
// TODO slots
|
||||
resolveSlots(instance, next.children)
|
||||
}
|
||||
const prevTree = instance.subTree
|
||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||
@ -551,7 +553,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
} else {
|
||||
if (isString(c1)) {
|
||||
hostSetElementText(container, '')
|
||||
if (c2 != null) {
|
||||
if (isArray(c2)) {
|
||||
mountChildren(c2, container, anchor)
|
||||
}
|
||||
} else if (isArray(c1)) {
|
||||
|
@ -10,13 +10,15 @@ export {
|
||||
export {
|
||||
ComponentOptions,
|
||||
FunctionalComponent,
|
||||
Slots,
|
||||
Slot,
|
||||
createComponent
|
||||
} from './component'
|
||||
|
||||
export * from './componentLifecycle'
|
||||
export { Slot, Slots } from './componentSlots'
|
||||
|
||||
export { ComponentPropsOptions } from './componentProps'
|
||||
|
||||
export { createRenderer, RendererOptions } from './createRenderer'
|
||||
export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||
export * from './reactivity'
|
||||
export * from './componentLifecycle'
|
||||
export { createRenderer, RendererOptions } from './createRenderer'
|
||||
|
||||
export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { isArray, EMPTY_ARR } from '@vue/shared'
|
||||
import { isArray, isFunction, isString, EMPTY_ARR } from '@vue/shared'
|
||||
import { ComponentInstance } from './component'
|
||||
import { HostNode } from './createRenderer'
|
||||
import { RawSlots } from './componentSlots'
|
||||
|
||||
export const Fragment = Symbol('Fragment')
|
||||
export const Text = Symbol('Text')
|
||||
@ -19,11 +20,13 @@ type VNodeChildAtom = VNode | string | number | null | void
|
||||
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
|
||||
export type VNodeChild = VNodeChildAtom | VNodeChildren
|
||||
|
||||
export type NormalizedChildren = string | VNodeChildren | RawSlots | null
|
||||
|
||||
export interface VNode {
|
||||
type: VNodeTypes
|
||||
props: { [key: string]: any } | null
|
||||
key: string | number | null
|
||||
children: string | VNodeChildren | null
|
||||
children: NormalizedChildren
|
||||
component: ComponentInstance | null
|
||||
|
||||
// DOM
|
||||
@ -91,7 +94,7 @@ export function createVNode(
|
||||
type,
|
||||
props,
|
||||
key: props && props.key,
|
||||
children: typeof children === 'number' ? children + '' : children,
|
||||
children: normalizeChildren(children),
|
||||
component: null,
|
||||
el: null,
|
||||
anchor: null,
|
||||
@ -127,10 +130,25 @@ export function normalizeVNode(child: VNodeChild): VNode {
|
||||
// fragment
|
||||
return createVNode(Fragment, null, child)
|
||||
} else if (typeof child === 'object') {
|
||||
// already vnode
|
||||
// already vnode, this should be the most common since compiled templates
|
||||
// always produce all-vnode children arrays
|
||||
return child as VNode
|
||||
} else {
|
||||
// primitive types
|
||||
return createVNode(Text, null, child + '')
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeChildren(children: unknown): NormalizedChildren {
|
||||
if (children == null) {
|
||||
return null
|
||||
} else if (isArray(children)) {
|
||||
return children
|
||||
} else if (typeof children === 'object') {
|
||||
return children as RawSlots
|
||||
} else if (isFunction(children)) {
|
||||
return { default: children }
|
||||
} else {
|
||||
return isString(children) ? children : children + ''
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user