wip: slots

This commit is contained in:
Evan You 2019-05-31 18:07:43 +08:00
parent e0a66d0381
commit c0c06813a7
6 changed files with 110 additions and 34 deletions

View File

@ -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

View File

@ -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 {

View 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
}
}

View File

@ -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)) {

View File

@ -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'

View File

@ -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 + ''
}
}