type Flatten<T> = { [K in keyof T]: T[K] }
-export interface ComponentClass extends Flatten<typeof Component> {
+export interface ComponentClass extends Flatten<typeof InternalComponent> {
new <D = Data, P = Data>(): MountedComponent<D, P> & D & P
}
// this interface is merged with the class type
// to represent a mounted component
-export interface MountedComponent<D = Data, P = Data> extends Component {
+export interface MountedComponent<D = Data, P = Data>
+ extends InternalComponent {
$vnode: VNode
$data: D
$props: P
_self: MountedComponent<D, P> // on proxies only
}
-export class Component {
+class InternalComponent {
public static options?: ComponentOptions
public get $el(): RenderNode | RenderFragment | null {
$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)
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)
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
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) {
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
} 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 (
--- /dev/null
+import { observable } from '@vue/observer'
+import { Component } from './component'
+import { Slots } from './vdom'
+
+const contextStore = observable() as Record<string, any>
+
+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])
+ }
+}
}
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
}
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'
// 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'
// slots
let slots: any
- if (childFlags == null) {
+ if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
childFlags = children
? ChildrenFlags.DYNAMIC_SLOTS
: ChildrenFlags.NO_CHILDREN
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)
}