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)) {
 | 
			
		||||
    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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user