Browse Source

feat(runtime-core): set context for manual slot functions as well

Evan You 6 years ago
parent
commit
8a58dce603

+ 3 - 0
packages/runtime-core/src/component.ts

@@ -494,6 +494,9 @@ const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
       if (__DEV__) {
         markAttrsAccessed()
       }
+      // if the user pass the slots proxy to h(), normalizeChildren should not
+      // attempt to attach ctx to the object
+      if (key === '_') return 1
       return instance[type][key]
     },
     has: (instance, key) => key === SetupProxySymbol || key in instance[type],

+ 22 - 14
packages/runtime-core/src/componentSlots.ts

@@ -8,6 +8,7 @@ import {
 import { isArray, isFunction, EMPTY_OBJ, ShapeFlags } from '@vue/shared'
 import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
+import { withCtx } from './helpers/withRenderContext'
 
 export type Slot = (...args: any[]) => VNode[]
 
@@ -21,6 +22,9 @@ export type RawSlots = {
   [name: string]: unknown
   // manual render fn hint to skip forced children updates
   $stable?: boolean
+  // internal, for tracking slot owner instance. This is attached during
+  // normalizeChildren when the component vnode is created.
+  _ctx?: ComponentInternalInstance | null
   // internal, indicates compiler generated slots = can skip normalization
   _?: 1
 }
@@ -30,18 +34,21 @@ const normalizeSlotValue = (value: unknown): VNode[] =>
     ? value.map(normalizeVNode)
     : [normalizeVNode(value as VNodeChild)]
 
-const normalizeSlot = (key: string, rawSlot: Function): Slot => (
-  props: any
-) => {
-  if (__DEV__ && currentInstance != null) {
-    warn(
-      `Slot "${key}" invoked outside of the render function: ` +
-        `this will not track dependencies used in the slot. ` +
-        `Invoke the slot function inside the render function instead.`
-    )
-  }
-  return normalizeSlotValue(rawSlot(props))
-}
+const normalizeSlot = (
+  key: string,
+  rawSlot: Function,
+  ctx: ComponentInternalInstance | null | undefined
+): Slot =>
+  withCtx((props: any) => {
+    if (__DEV__ && currentInstance != null) {
+      warn(
+        `Slot "${key}" invoked outside of the render function: ` +
+          `this will not track dependencies used in the slot. ` +
+          `Invoke the slot function inside the render function instead.`
+      )
+    }
+    return normalizeSlotValue(rawSlot(props))
+  }, ctx)
 
 export function resolveSlots(
   instance: ComponentInternalInstance,
@@ -55,11 +62,12 @@ export function resolveSlots(
       slots = children as Slots
     } else {
       slots = {}
+      const ctx = rawSlots._ctx
       for (const key in rawSlots) {
-        if (key === '$stable') continue
+        if (key === '$stable' || key === '_ctx') continue
         const value = rawSlots[key]
         if (isFunction(value)) {
-          slots[key] = normalizeSlot(key, value)
+          slots[key] = normalizeSlot(key, value, ctx)
         } else if (value != null) {
           if (__DEV__) {
             warn(

+ 2 - 3
packages/runtime-core/src/helpers/scopeId.ts

@@ -24,13 +24,12 @@ export function popScopeId() {
 export function withScopeId(id: string): <T extends Function>(fn: T) => T {
   if (__BUNDLER__) {
     return ((fn: Function, ctx?: ComponentInternalInstance) => {
-      function renderWithId(this: any) {
+      return withCtx(function(this: any) {
         pushScopeId(id)
         const res = fn.apply(this, arguments)
         popScopeId()
         return res
-      }
-      return ctx ? withCtx(renderWithId, ctx) : renderWithId
+      }, ctx)
     }) as any
   } else {
     return undefined as any

+ 5 - 1
packages/runtime-core/src/helpers/withRenderContext.ts

@@ -5,7 +5,11 @@ import {
   currentRenderingInstance
 } from '../componentRenderUtils'
 
-export function withCtx(fn: Slot, ctx: ComponentInternalInstance) {
+export function withCtx(
+  fn: Slot,
+  ctx: ComponentInternalInstance | null | undefined
+) {
+  if (!ctx) return fn
   return function renderFnWithContext() {
     const owner = currentRenderingInstance
     setCurrentRenderingInstance(ctx)

+ 5 - 1
packages/runtime-core/src/vnode.ts

@@ -30,6 +30,7 @@ import { TransitionHooks } from './components/BaseTransition'
 import { warn } from './warning'
 import { currentScopeId } from './helpers/scopeId'
 import { PortalImpl, isPortal } from './components/Portal'
+import { currentRenderingInstance } from './componentRenderUtils'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -387,8 +388,11 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
     type = ShapeFlags.ARRAY_CHILDREN
   } else if (typeof children === 'object') {
     type = ShapeFlags.SLOTS_CHILDREN
+    if (!(children as RawSlots)._) {
+      ;(children as RawSlots)._ctx = currentRenderingInstance
+    }
   } else if (isFunction(children)) {
-    children = { default: children }
+    children = { default: children, _ctx: currentRenderingInstance }
     type = ShapeFlags.SLOTS_CHILDREN
   } else {
     children = String(children)