fix: provide/inject should be resolved in parent tree
This commit is contained in:
parent
e4e138197c
commit
d4cd3fb352
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user