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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user