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> {
|
interface APIMethods<P, D> {
|
||||||
data?(): Partial<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 {
|
interface LifecycleMethods {
|
||||||
@ -76,7 +76,7 @@ export interface ComponentClass extends ComponentClassOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionalComponent<P = {}> {
|
export interface FunctionalComponent<P = {}> {
|
||||||
(props: P, slots: Slots, attrs: Data): any
|
(props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
|
||||||
pure?: boolean
|
pure?: boolean
|
||||||
props?: ComponentPropsOptions<P>
|
props?: ComponentPropsOptions<P>
|
||||||
displayName?: string
|
displayName?: string
|
||||||
|
@ -107,7 +107,8 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
|||||||
instance.$proxy,
|
instance.$proxy,
|
||||||
instance.$props,
|
instance.$props,
|
||||||
instance.$slots,
|
instance.$slots,
|
||||||
instance.$attrs
|
instance.$attrs,
|
||||||
|
instance.$parentVNode
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, instance, ErrorTypes.RENDER)
|
handleError(err, instance, ErrorTypes.RENDER)
|
||||||
@ -120,7 +121,7 @@ export function renderFunctionalRoot(vnode: VNode): VNode {
|
|||||||
const { props, attrs } = resolveProps(vnode.data, render.props)
|
const { props, attrs } = resolveProps(vnode.data, render.props)
|
||||||
let subTree
|
let subTree
|
||||||
try {
|
try {
|
||||||
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ)
|
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ, vnode)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, vnode, ErrorTypes.RENDER)
|
handleError(err, vnode, ErrorTypes.RENDER)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { observable } from '@vue/observer'
|
import { observable } from '@vue/observer'
|
||||||
import { Component } from '../component'
|
import { Component, FunctionalComponent, ComponentInstance } from '../component'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
import { Slots, VNode } from '../vdom'
|
||||||
const contextStore = observable() as Record<string | symbol, any>
|
import { VNodeFlags } from '../flags'
|
||||||
|
|
||||||
interface ProviderProps {
|
interface ProviderProps {
|
||||||
id: string | symbol
|
id: string | symbol
|
||||||
@ -10,6 +10,8 @@ interface ProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Provide extends Component<ProviderProps> {
|
export class Provide extends Component<ProviderProps> {
|
||||||
|
context: Record<string | symbol, any> = observable()
|
||||||
|
|
||||||
static props = {
|
static props = {
|
||||||
id: {
|
id: {
|
||||||
type: [String, Symbol],
|
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() {
|
created() {
|
||||||
if (__DEV__) {
|
const { $props, context } = this
|
||||||
const { id } = this.$props
|
this.$watch(
|
||||||
if (contextStore.hasOwnProperty(id)) {
|
() => $props.value,
|
||||||
warn(`A context provider with id ${id.toString()} already exists.`)
|
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.$watch(
|
||||||
() => this.$props.id,
|
() => $props.id,
|
||||||
(id: string, oldId: string) => {
|
(id, oldId) => {
|
||||||
warn(
|
warn(
|
||||||
`Context provider id change detected (from "${oldId}" to "${id}"). ` +
|
`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) {
|
render(props: any, slots: any) {
|
||||||
return slots.default && slots.default()
|
return slots.default && slots.default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Inject extends Component {
|
interface InjectProps {
|
||||||
render(props: any, slots: any) {
|
id: string | symbol
|
||||||
return slots.default && slots.default(contextStore[props.id])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[]) {
|
export function warn(msg: string, ...args: any[]) {
|
||||||
// TODO warn handler?
|
// TODO warn handler?
|
||||||
warn(`[Vue warn]: ${msg}${getComponentTrace()}`, ...args)
|
console.warn(`[Vue warn]: ${msg}${getComponentTrace()}`, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComponentTrace(): string {
|
function getComponentTrace(): string {
|
||||||
|
Loading…
Reference in New Issue
Block a user