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 {
}
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
instance.$proxy,
instance.$props,
instance.$slots,
- instance.$attrs
+ instance.$attrs,
+ instance.$parentVNode
)
} catch (err) {
handleError(err, instance, ErrorTypes.RENDER)
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)
}
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
}
export class Provide extends Component<ProviderProps> {
+ context: Record<string | symbol, any> = observable()
+
static props = {
id: {
type: [String, Symbol],
}
}
- 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