feat: provide/inject

This commit is contained in:
Evan You 2018-09-25 17:49:47 -04:00
parent 1e447d021b
commit 7484b4d2e6
6 changed files with 101 additions and 18 deletions

View File

@ -14,7 +14,7 @@ import { ErrorTypes } from './errorHandling'
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
}
@ -26,7 +26,8 @@ export interface FunctionalComponent<P = Data> extends RenderFunction<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
@ -60,7 +61,7 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
_self: MountedComponent<D, P> // 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

View File

@ -103,7 +103,16 @@ export function normalizeComponentRoot(
} else if (typeof vnode !== 'object') {
vnode = createTextVNode(vnode + '')
} else if (Array.isArray(vnode)) {
if (vnode.length === 1) {
vnode = normalizeComponentRoot(
vnode[0],
componentVNode,
attrs,
inheritAttrs
)
} else {
vnode = createFragment(vnode)
}
} else {
const { flags } = vnode
if (

View File

@ -0,0 +1,57 @@
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])
}
}

View File

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

View File

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

View File

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