]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: slots
authorEvan You <yyx990803@gmail.com>
Fri, 31 May 2019 10:07:43 +0000 (18:07 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 31 May 2019 10:07:43 +0000 (18:07 +0800)
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentSlots.ts [new file with mode: 0644]
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/vnode.ts

index 496ee37c1f0ffbadaa9b32a7dc659234da60f664..f13d9ac46d838b79c02a3c65a96605aa84c9ce6b 100644 (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
index e2aad8dabc4db93c02ff6aa66bf7668ace2aa5c6..35fafc8ae4025152109f8e6e68a433f331324bbb 100644 (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 {
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
new file mode 100644 (file)
index 0000000..65ca984
--- /dev/null
@@ -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
+  }
+}
index deda9abb15db298059933d59c3aa9fa13f63c71a..fa7a6175508b546199f23fdc8f832c9ac7da01fa 100644 (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)) {
index 2069e2f9bb0f9449080db92083e5fdb702b75dfe..df111f70637136fe8ce847065b8faadc6b57d61b 100644 (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 * from './reactivity'
+export * from './componentLifecycle'
 export { createRenderer, RendererOptions } from './createRenderer'
+
 export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-export * from './reactivity'
index 6ac858230c24a3a2ad2e1b5d0e8efa9ab08f01d0..d6f9e3c287cb34d6c9a49ed20f74cef7fe3daa69 100644 (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 + ''
+  }
+}