feat: provide/inject
This commit is contained in:
parent
1e447d021b
commit
7484b4d2e6
@ -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
|
||||
|
@ -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 (
|
||||
|
57
packages/core/src/context.ts
Normal file
57
packages/core/src/context.ts
Normal 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])
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user