wip: slots
This commit is contained in:
parent
e0a66d0381
commit
c0c06813a7
@ -7,19 +7,12 @@ import {
|
|||||||
} from '@vue/observer'
|
} from '@vue/observer'
|
||||||
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
||||||
import { RenderProxyHandlers } from './componentProxy'
|
import { RenderProxyHandlers } from './componentProxy'
|
||||||
import { ComponentPropsOptions, PropValidator } from './componentProps'
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
|
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
|
||||||
|
import { Slots } from './componentSlots'
|
||||||
|
|
||||||
export type Data = { [key: string]: any }
|
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> = {
|
export type ComponentPublicProperties<P = Data, S = Data> = {
|
||||||
$state: S
|
$state: S
|
||||||
$props: P
|
$props: P
|
||||||
@ -70,12 +63,6 @@ export interface LifecycleHooks {
|
|||||||
ec: LifecycleHook // errorCaptured
|
ec: LifecycleHook // errorCaptured
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Slot = (...args: any[]) => VNode[]
|
|
||||||
|
|
||||||
export type Slots = Readonly<{
|
|
||||||
[name: string]: Slot
|
|
||||||
}>
|
|
||||||
|
|
||||||
export type ComponentInstance<P = Data, S = Data> = {
|
export type ComponentInstance<P = Data, S = Data> = {
|
||||||
type: FunctionalComponent | ComponentOptions
|
type: FunctionalComponent | ComponentOptions
|
||||||
vnode: VNode
|
vnode: VNode
|
||||||
|
@ -16,17 +16,25 @@ export type ComponentPropsOptions<P = Data> = {
|
|||||||
[K in keyof P]: PropValidator<P[K]>
|
[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
|
type?: PropType<T> | true | null
|
||||||
required?: boolean
|
required?: boolean
|
||||||
default?: T | null | undefined | (() => T | null | undefined)
|
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 {
|
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 { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
||||||
import { resolveProps } from './componentProps'
|
import { resolveProps } from './componentProps'
|
||||||
|
import { resolveSlots } from './componentSlots'
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
@ -201,7 +202,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
if (isString(vnode.children)) {
|
if (isString(vnode.children)) {
|
||||||
hostSetElementText(el, vnode.children)
|
hostSetElementText(el, vnode.children)
|
||||||
} else if (vnode.children != null) {
|
} else if (isArray(vnode.children)) {
|
||||||
mountChildren(vnode.children, el)
|
mountChildren(vnode.children, el)
|
||||||
}
|
}
|
||||||
hostInsert(el, container, anchor)
|
hostInsert(el, container, anchor)
|
||||||
@ -382,7 +383,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (target != null) {
|
if (target != null) {
|
||||||
if (isString(children)) {
|
if (isString(children)) {
|
||||||
hostSetElementText(target, children)
|
hostSetElementText(target, children)
|
||||||
} else if (children != null) {
|
} else if (isArray(children)) {
|
||||||
mountChildren(children, target)
|
mountChildren(children, target)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -407,7 +408,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (isString(children)) {
|
if (isString(children)) {
|
||||||
hostSetElementText(target, '')
|
hostSetElementText(target, '')
|
||||||
hostSetElementText(nextTarget, children)
|
hostSetElementText(nextTarget, children)
|
||||||
} else if (children != null) {
|
} else if (isArray(children)) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
move(children[i] as VNode, nextTarget, null)
|
move(children[i] as VNode, nextTarget, null)
|
||||||
}
|
}
|
||||||
@ -454,6 +455,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// initial mount
|
// initial mount
|
||||||
instance.vnode = vnode
|
instance.vnode = vnode
|
||||||
resolveProps(instance, vnode.props, Component.props)
|
resolveProps(instance, vnode.props, Component.props)
|
||||||
|
resolveSlots(instance, vnode.children)
|
||||||
// setup stateful
|
// setup stateful
|
||||||
if (typeof Component === 'object') {
|
if (typeof Component === 'object') {
|
||||||
setupStatefulComponent(instance)
|
setupStatefulComponent(instance)
|
||||||
@ -479,7 +481,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
instance.vnode = next
|
instance.vnode = next
|
||||||
instance.next = null
|
instance.next = null
|
||||||
resolveProps(instance, next.props, Component.props)
|
resolveProps(instance, next.props, Component.props)
|
||||||
// TODO slots
|
resolveSlots(instance, next.children)
|
||||||
}
|
}
|
||||||
const prevTree = instance.subTree
|
const prevTree = instance.subTree
|
||||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||||
@ -551,7 +553,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
} else {
|
} else {
|
||||||
if (isString(c1)) {
|
if (isString(c1)) {
|
||||||
hostSetElementText(container, '')
|
hostSetElementText(container, '')
|
||||||
if (c2 != null) {
|
if (isArray(c2)) {
|
||||||
mountChildren(c2, container, anchor)
|
mountChildren(c2, container, anchor)
|
||||||
}
|
}
|
||||||
} else if (isArray(c1)) {
|
} else if (isArray(c1)) {
|
||||||
|
@ -10,13 +10,15 @@ export {
|
|||||||
export {
|
export {
|
||||||
ComponentOptions,
|
ComponentOptions,
|
||||||
FunctionalComponent,
|
FunctionalComponent,
|
||||||
Slots,
|
|
||||||
Slot,
|
|
||||||
createComponent
|
createComponent
|
||||||
} from './component'
|
} 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 './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 { ComponentInstance } from './component'
|
||||||
import { HostNode } from './createRenderer'
|
import { HostNode } from './createRenderer'
|
||||||
|
import { RawSlots } from './componentSlots'
|
||||||
|
|
||||||
export const Fragment = Symbol('Fragment')
|
export const Fragment = Symbol('Fragment')
|
||||||
export const Text = Symbol('Text')
|
export const Text = Symbol('Text')
|
||||||
@ -19,11 +20,13 @@ type VNodeChildAtom = VNode | string | number | null | void
|
|||||||
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
|
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
|
||||||
export type VNodeChild = VNodeChildAtom | VNodeChildren
|
export type VNodeChild = VNodeChildAtom | VNodeChildren
|
||||||
|
|
||||||
|
export type NormalizedChildren = string | VNodeChildren | RawSlots | null
|
||||||
|
|
||||||
export interface VNode {
|
export interface VNode {
|
||||||
type: VNodeTypes
|
type: VNodeTypes
|
||||||
props: { [key: string]: any } | null
|
props: { [key: string]: any } | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
children: string | VNodeChildren | null
|
children: NormalizedChildren
|
||||||
component: ComponentInstance | null
|
component: ComponentInstance | null
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
@ -91,7 +94,7 @@ export function createVNode(
|
|||||||
type,
|
type,
|
||||||
props,
|
props,
|
||||||
key: props && props.key,
|
key: props && props.key,
|
||||||
children: typeof children === 'number' ? children + '' : children,
|
children: normalizeChildren(children),
|
||||||
component: null,
|
component: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
@ -127,10 +130,25 @@ export function normalizeVNode(child: VNodeChild): VNode {
|
|||||||
// fragment
|
// fragment
|
||||||
return createVNode(Fragment, null, child)
|
return createVNode(Fragment, null, child)
|
||||||
} else if (typeof child === 'object') {
|
} 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
|
return child as VNode
|
||||||
} else {
|
} else {
|
||||||
// primitive types
|
// primitive types
|
||||||
return createVNode(Text, null, child + '')
|
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