From d4cd3fb3524959269fd47f4abe62cf73913a6aab Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 15 Oct 2018 13:12:13 -0400 Subject: [PATCH] fix: provide/inject should be resolved in parent tree --- packages/core/src/component.ts | 4 +- packages/core/src/componentUtils.ts | 5 +- packages/core/src/optional/context.ts | 85 +++++++++++++++++++-------- packages/core/src/warning.ts | 2 +- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 371cd915..6d5d4bfa 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -46,7 +46,7 @@ interface PublicInstanceMethods { interface APIMethods { data?(): Partial - render(props: Readonly

, slots: Slots, attrs: Data): any + render(props: Readonly

, slots: Slots, attrs: Data, parentVNode: VNode): any } interface LifecycleMethods { @@ -76,7 +76,7 @@ export interface ComponentClass extends ComponentClassOptions { } export interface FunctionalComponent

{ - (props: P, slots: Slots, attrs: Data): any + (props: P, slots: Slots, attrs: Data, parentVNode: VNode): any pure?: boolean props?: ComponentPropsOptions

displayName?: string diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index bb91a6b1..0aebc6b3 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -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) } diff --git a/packages/core/src/optional/context.ts b/packages/core/src/optional/context.ts index 6ab92fac..424bc5e0 100644 --- a/packages/core/src/optional/context.ts +++ b/packages/core/src/optional/context.ts @@ -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 +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 { + context: Record = observable() + static props = { id: { type: [String, Symbol], @@ -20,40 +22,75 @@ export class Provide extends Component { } } - 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 = ( + 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 diff --git a/packages/core/src/warning.ts b/packages/core/src/warning.ts index 6bda13d4..e014924a 100644 --- a/packages/core/src/warning.ts +++ b/packages/core/src/warning.ts @@ -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 {