diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index cf5d7c5cf..832201fce 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -14,7 +14,7 @@ import { ErrorTypes } from './errorHandling' type Flatten = { [K in keyof T]: T[K] } -export interface ComponentClass extends Flatten { +export interface ComponentClass extends Flatten { new (): MountedComponent & D & P } @@ -26,7 +26,8 @@ export interface FunctionalComponent

extends RenderFunction

{ // this interface is merged with the class type // to represent a mounted component -export interface MountedComponent extends Component { +export interface MountedComponent + extends InternalComponent { $vnode: VNode $data: D $props: P @@ -60,7 +61,7 @@ export interface MountedComponent extends Component { _self: MountedComponent // on proxies only } -export class Component { +class InternalComponent { public static options?: ComponentOptions public get $el(): RenderNode | RenderFragment | null { @@ -108,14 +109,14 @@ export class Component { $watch( this: MountedComponent, keyOrFn: string | (() => any), - cb: () => void, + cb: (newValue: any, oldValue: any) => void, options?: WatchOptions ) { return setupWatcher(this, keyOrFn, cb, options) } // eventEmitter interface - $on(event: string, fn: Function): Component { + $on(this: MountedComponent, event: string, fn: Function): MountedComponent { if (Array.isArray(event)) { for (let i = 0; i < event.length; i++) { this.$on(event[i], fn) @@ -127,7 +128,7 @@ export class Component { return this } - $once(event: string, fn: Function): Component { + $once(this: MountedComponent, event: string, fn: Function): MountedComponent { const onceFn = (...args: any[]) => { this.$off(event, onceFn) fn.apply(this, args) @@ -136,7 +137,11 @@ export class Component { return this.$on(event, onceFn) } - $off(event?: string, fn?: Function) { + $off( + this: MountedComponent, + event?: string, + fn?: Function + ): MountedComponent { if (this._events) { if (!event && !fn) { this._events = null @@ -162,7 +167,11 @@ export class Component { return this } - $emit(this: MountedComponent, name: string, ...payload: any[]) { + $emit( + this: MountedComponent, + name: string, + ...payload: any[] + ): MountedComponent { const parentListener = this.$props['on' + name] || this.$props['on' + name.toLowerCase()] if (parentListener) { @@ -188,3 +197,7 @@ function invokeListeners(value: Function | Function[], payload: any[]) { value(...payload) } } + +// the exported Component has the implementation details of the actual +// InternalComponent class but with proper type inference of ComponentClass. +export const Component = InternalComponent as ComponentClass diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index fe191e16e..e8535a28c 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -103,7 +103,16 @@ export function normalizeComponentRoot( } else if (typeof vnode !== 'object') { vnode = createTextVNode(vnode + '') } else if (Array.isArray(vnode)) { - vnode = createFragment(vnode) + if (vnode.length === 1) { + vnode = normalizeComponentRoot( + vnode[0], + componentVNode, + attrs, + inheritAttrs + ) + } else { + vnode = createFragment(vnode) + } } else { const { flags } = vnode if ( diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts new file mode 100644 index 000000000..252e64802 --- /dev/null +++ b/packages/core/src/context.ts @@ -0,0 +1,57 @@ +import { observable } from '@vue/observer' +import { Component } from './component' +import { Slots } from './vdom' + +const contextStore = observable() as Record + +export class Provide extends Component { + updateValue() { + contextStore[this.$props.id] = this.$props.value + } + created() { + if (__DEV__) { + if (contextStore.hasOwnProperty(this.$props.id)) { + console.warn( + `A context provider with id ${this.$props.id} already exists.` + ) + } + this.$watch( + () => this.$props.id, + (id: string, oldId: string) => { + console.warn( + `Context provider id change detected (from "${oldId}" to "${id}"). ` + + `This is not supported and should be avoided.` + ) + }, + { sync: true } + ) + } + this.updateValue() + } + beforeUpdate() { + this.updateValue() + } + render(_: any, slots: Slots) { + return slots.default && slots.default() + } +} + +if (__DEV__) { + Provide.options = { + props: { + id: { + type: String, + required: true + }, + value: { + required: true + } + } + } +} + +export class Inject extends Component { + render(props: any, slots: Slots) { + return slots.default && slots.default(contextStore[props.id]) + } +} diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts index 6888207b9..e07fbc669 100644 --- a/packages/core/src/h.ts +++ b/packages/core/src/h.ts @@ -32,7 +32,10 @@ export interface createElement { } export const h = ((tag: ElementType, data?: any, children?: any): VNode => { - if (Array.isArray(data) || (data !== void 0 && typeof data !== 'object')) { + if ( + Array.isArray(data) || + (data != null && (typeof data !== 'object' || data._isVNode)) + ) { children = data data = null } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d2312b93d..5e86a6a8e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,10 +2,7 @@ export { h, Fragment, Portal } from './h' export { cloneVNode, createPortal, createFragment } from './vdom' export { createRenderer } from './createRenderer' - -import { Component as InternalComponent, ComponentClass } from './component' -// the public component constructor with proper type inference. -export const Component = InternalComponent as ComponentClass +export { Component } from './component' // observer api export * from '@vue/observer' @@ -15,7 +12,10 @@ export { nextTick } from '@vue/scheduler' // internal api export { createComponentInstance } from './componentUtils' + +// import-on-demand apis export { applyDirective } from './directive' +export { Provide, Inject } from './context' // flags & types export { ComponentClass, FunctionalComponent } from './component' diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts index e85d1b31f..72a71c451 100644 --- a/packages/core/src/vdom.ts +++ b/packages/core/src/vdom.ts @@ -169,7 +169,7 @@ export function createComponentVNode( // slots let slots: any - if (childFlags == null) { + if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) { childFlags = children ? ChildrenFlags.DYNAMIC_SLOTS : ChildrenFlags.NO_CHILDREN @@ -178,11 +178,12 @@ export function createComponentVNode( if (childrenType === 'function') { // function as children slots = { default: children } - } else if (childrenType === 'object' && !(children as VNode)._isVNode) { + } else if (Array.isArray(children) || (children as VNode)._isVNode) { + // direct vnode children + slots = { default: () => children } + } else if (typeof children === 'object') { // slot object as children slots = children - } else { - slots = { default: () => children } } slots = normalizeSlots(slots) }