]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: provide/inject should be resolved in parent tree
authorEvan You <yyx990803@gmail.com>
Mon, 15 Oct 2018 17:12:13 +0000 (13:12 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 15 Oct 2018 17:12:13 +0000 (13:12 -0400)
packages/core/src/component.ts
packages/core/src/componentUtils.ts
packages/core/src/optional/context.ts
packages/core/src/warning.ts

index 371cd9158a05179f79cf995371d12a0c80190e88..6d5d4bfa5ec64412f897877cefef4226f8384d77 100644 (file)
@@ -46,7 +46,7 @@ interface PublicInstanceMethods {
 
 interface APIMethods<P, D> {
   data?(): Partial<D>
-  render(props: Readonly<P>, slots: Slots, attrs: Data): any
+  render(props: Readonly<P>, slots: Slots, attrs: Data, parentVNode: VNode): any
 }
 
 interface LifecycleMethods {
@@ -76,7 +76,7 @@ export interface ComponentClass extends ComponentClassOptions {
 }
 
 export interface FunctionalComponent<P = {}> {
-  (props: P, slots: Slots, attrs: Data): any
+  (props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
   pure?: boolean
   props?: ComponentPropsOptions<P>
   displayName?: string
index bb91a6b1974831203a73386f551d3c6ee5563123..0aebc6b3422c70a58016e969bb1674258e44db7f 100644 (file)
@@ -107,7 +107,8 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
       instance.$proxy,
       instance.$props,
       instance.$slots,
-      instance.$attrs
+      instance.$attrs,
+      instance.$parentVNode
     )
   } catch (err) {
     handleError(err, instance, ErrorTypes.RENDER)
@@ -120,7 +121,7 @@ export function renderFunctionalRoot(vnode: VNode): VNode {
   const { props, attrs } = resolveProps(vnode.data, render.props)
   let subTree
   try {
-    subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ)
+    subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ, vnode)
   } catch (err) {
     handleError(err, vnode, ErrorTypes.RENDER)
   }
index 6ab92face4bbf272c5b48b18fbcf4cb10313f009..424bc5e07f3e2f3cbc0d79d85cf4f2d8b18e1306 100644 (file)
@@ -1,8 +1,8 @@
 import { observable } from '@vue/observer'
-import { Component } from '../component'
+import { Component, FunctionalComponent, ComponentInstance } from '../component'
 import { warn } from '../warning'
-
-const contextStore = observable() as Record<string | symbol, any>
+import { Slots, VNode } from '../vdom'
+import { VNodeFlags } from '../flags'
 
 interface ProviderProps {
   id: string | symbol
@@ -10,6 +10,8 @@ interface ProviderProps {
 }
 
 export class Provide extends Component<ProviderProps> {
+  context: Record<string | symbol, any> = observable()
+
   static props = {
     id: {
       type: [String, Symbol],
@@ -20,40 +22,75 @@ export class Provide extends Component<ProviderProps> {
     }
   }
 
-  updateValue() {
-    // TS doesn't allow symbol as index :/
-    // https://github.com/Microsoft/TypeScript/issues/24587
-    contextStore[this.$props.id as string] = this.$props.value
-  }
   created() {
-    if (__DEV__) {
-      const { id } = this.$props
-      if (contextStore.hasOwnProperty(id)) {
-        warn(`A context provider with id ${id.toString()} already exists.`)
+    const { $props, context } = this
+    this.$watch(
+      () => $props.value,
+      value => {
+        // TS doesn't allow symbol as index :/
+        // https://github.com/Microsoft/TypeScript/issues/24587
+        context[$props.id as string] = value
+      },
+      {
+        sync: true,
+        immediate: true
       }
+    )
+    if (__DEV__) {
       this.$watch(
-        () => this.$props.id,
-        (id: string, oldId: string) => {
+        () => $props.id,
+        (id, oldId) => {
           warn(
             `Context provider id change detected (from "${oldId}" to "${id}"). ` +
-              `This is not supported and should be avoided.`
+              `This is not supported and should be avoided as it leads to ` +
+              `indeterministic context resolution.`
           )
         },
-        { sync: true }
+        {
+          sync: true
+        }
       )
     }
-    this.updateValue()
-  }
-  beforeUpdate() {
-    this.updateValue()
   }
+
   render(props: any, slots: any) {
     return slots.default && slots.default()
   }
 }
 
-export class Inject extends Component {
-  render(props: any, slots: any) {
-    return slots.default && slots.default(contextStore[props.id])
+interface InjectProps {
+  id: string | symbol
+}
+
+export const Inject: FunctionalComponent<InjectProps> = (
+  props: InjectProps,
+  slots: Slots,
+  attrs: any,
+  vnode: VNode
+) => {
+  let resolvedValue
+  let resolved = false
+  const { id } = props
+  // walk the parent chain to locate context with key
+  while (vnode !== null && vnode.contextVNode !== null) {
+    if (
+      vnode.flags & VNodeFlags.COMPONENT_STATEFUL &&
+      (vnode.children as ComponentInstance).constructor === Provide &&
+      (vnode.children as any).context.hasOwnProperty(id)
+    ) {
+      resolved = true
+      resolvedValue = (vnode.children as any).context[id]
+      break
+    }
+    vnode = vnode.contextVNode
+  }
+  if (__DEV__ && !resolved) {
+    warn(
+      `Inject with id "${id.toString()}" did not match any Provider with ` +
+        `corresponding property in the parent chain.`
+    )
   }
+  return slots.default && slots.default(resolvedValue)
 }
+
+Inject.pure = true
index 6bda13d4cda40bd44cfb8c274d93ff67b7808b7a..e014924ad7bd9aefa8eeb6ab7a739e7d23cf1765 100644 (file)
@@ -14,7 +14,7 @@ export function popWarningContext() {
 
 export function warn(msg: string, ...args: any[]) {
   // TODO warn handler?
-  warn(`[Vue warn]: ${msg}${getComponentTrace()}`, ...args)
+  console.warn(`[Vue warn]: ${msg}${getComponentTrace()}`, ...args)
 }
 
 function getComponentTrace(): string {