fix: provide/inject should be resolved in parent tree

This commit is contained in:
Evan You 2018-10-15 13:12:13 -04:00
parent e4e138197c
commit d4cd3fb352
4 changed files with 67 additions and 29 deletions

View 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

View 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)
}

View 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() {
created() {
const { $props, context } = this
this.$watch(
() => $props.value,
value => {
// TS doesn't allow symbol as index :/
// https://github.com/Microsoft/TypeScript/issues/24587
contextStore[this.$props.id as string] = this.$props.value
context[$props.id as string] = value
},
{
sync: true,
immediate: true
}
created() {
)
if (__DEV__) {
const { id } = this.$props
if (contextStore.hasOwnProperty(id)) {
warn(`A context provider with id ${id.toString()} already exists.`)
}
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

View 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 {