]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: provide/inject
authorEvan You <yyx990803@gmail.com>
Tue, 25 Sep 2018 21:49:47 +0000 (17:49 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 25 Sep 2018 21:49:47 +0000 (17:49 -0400)
packages/core/src/component.ts
packages/core/src/componentUtils.ts
packages/core/src/context.ts [new file with mode: 0644]
packages/core/src/h.ts
packages/core/src/index.ts
packages/core/src/vdom.ts

index cf5d7c5cf6512f896befddd5842e25761cab8612..832201fce5782d0491bbd03f70f979ee968ac3d1 100644 (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
index fe191e16e3fc40ddbcc0bbb55d7e11dcee3d2fbe..e8535a28cf72a78f39c14be6ba86d016ef2ae650 100644 (file)
@@ -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 (file)
index 0000000..252e648
--- /dev/null
@@ -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])
+  }
+}
index 6888207b9ebba26b85737af744d542ced34279a6..e07fbc669e071e205b98a9c9e5ebd317343fa0df 100644 (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
   }
index d2312b93d35f46c89e209b1e8b4717b9f9fcf260..5e86a6a8efa7934712ce4b47a9e1589830236998 100644 (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'
index e85d1b31f8c03a16db7b43c709fbc9441c139f1c..72a71c451b77624538aaaaf3fb14b2dda0f067a7 100644 (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)
     }