From 2e39f3e241184a8ce263f619ef5ec1ce259fb1ec Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka <rizumu@ayaka.moe> Date: Tue, 26 Mar 2024 21:40:51 +0800 Subject: [PATCH 1/2] feat(runtime-vapor): the current instance should be kept in the slot --- .../__tests__/componentSlots.spec.ts | 30 +++++++++++++++++++ .../src/componentRenderContext.ts | 22 ++++++++++++++ packages/runtime-vapor/src/index.ts | 1 + 3 files changed, 53 insertions(+) create mode 100644 packages/runtime-vapor/src/componentRenderContext.ts diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index ec8788060..959d8e821 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -8,6 +8,7 @@ import { nextTick, ref, template, + withCtx, } from '../src' import { makeRender } from './_utils' @@ -152,6 +153,35 @@ describe('component: slots', () => { expect(instance.slots).toHaveProperty('footer') }) + test('the current instance should be kept in the slot', async () => { + let instanceInSlot: any + + const Comp = defineComponent({ + render() { + const instance = getCurrentInstance() + instance!.slots.default!() + return template('<div></div>')() + }, + }) + + const { instance } = define({ + render() { + return createComponent( + Comp, + {}, + { + default: withCtx(() => { + instanceInSlot = getCurrentInstance() + return template('content')() + }), + }, + ) + }, + }).render() + + expect(instanceInSlot).toBe(instance) + }) + test.todo('should respect $stable flag', async () => { // TODO: $stable flag? }) diff --git a/packages/runtime-vapor/src/componentRenderContext.ts b/packages/runtime-vapor/src/componentRenderContext.ts new file mode 100644 index 000000000..e8051a360 --- /dev/null +++ b/packages/runtime-vapor/src/componentRenderContext.ts @@ -0,0 +1,22 @@ +import { + type ComponentInternalInstance, + currentInstance, + setCurrentInstance, +} from './component' + +export function withCtx<T extends (...args: any) => any>( + fn: T, + instance: ComponentInternalInstance | null = currentInstance, +): T { + if (!instance) return fn + + const fnWithCtx = ((...args: any[]) => { + const reset = setCurrentInstance(instance) + try { + return fn(...args) + } finally { + reset() + } + }) as T + return fnWithCtx +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index b15f4c461..81f190544 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -50,6 +50,7 @@ export { type FunctionalComponent, type SetupFn, } from './component' +export { withCtx } from './componentRenderContext' export { renderEffect } from './renderEffect' export { watch, From 3aba5c9e0de2514de61b405211b13bafd575bc57 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka <rizumu@ayaka.moe> Date: Sat, 6 Apr 2024 17:17:30 +0800 Subject: [PATCH 2/2] refactor: wrap the ctx when initializing the slots --- .../__tests__/componentSlots.spec.ts | 61 ++++++++++++------- .../src/componentRenderContext.ts | 22 ------- packages/runtime-vapor/src/componentSlots.ts | 46 ++++++++++---- packages/runtime-vapor/src/index.ts | 1 - 4 files changed, 73 insertions(+), 57 deletions(-) delete mode 100644 packages/runtime-vapor/src/componentRenderContext.ts diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 959d8e821..c0e1b716f 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -8,7 +8,6 @@ import { nextTick, ref, template, - withCtx, } from '../src' import { makeRender } from './_utils' @@ -35,21 +34,6 @@ function renderWithSlots(slots: any): any { } describe('component: slots', () => { - test('initSlots: instance.slots should be set correctly', () => { - const { slots } = renderWithSlots({ _: 1 }) - expect(slots).toMatchObject({ _: 1 }) - }) - - // NOTE: slot normalization is not supported - test.todo( - 'initSlots: should normalize object slots (when value is null, string, array)', - () => {}, - ) - test.todo( - 'initSlots: should normalize object slots (when value is function)', - () => {}, - ) - test('initSlots: instance.slots should be set correctly', () => { let instance: any const Comp = defineComponent({ @@ -74,6 +58,16 @@ describe('component: slots', () => { ) }) + // NOTE: slot normalization is not supported + test.todo( + 'initSlots: should normalize object slots (when value is null, string, array)', + () => {}, + ) + test.todo( + 'initSlots: should normalize object slots (when value is function)', + () => {}, + ) + // runtime-core's "initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)" test('initSlots: instance.slots should be set correctly', () => { const { slots } = renderWithSlots({ @@ -154,12 +148,16 @@ describe('component: slots', () => { }) test('the current instance should be kept in the slot', async () => { - let instanceInSlot: any + let instanceInDefaultSlot: any + let instanceInVForSlot: any + let instanceInVIfSlot: any const Comp = defineComponent({ render() { const instance = getCurrentInstance() instance!.slots.default!() + instance!.slots.inVFor!() + instance!.slots.inVIf!() return template('<div></div>')() }, }) @@ -170,16 +168,37 @@ describe('component: slots', () => { Comp, {}, { - default: withCtx(() => { - instanceInSlot = getCurrentInstance() + default: () => { + instanceInDefaultSlot = getCurrentInstance() return template('content')() - }), + }, }, + () => [ + [ + { + name: 'inVFor', + fn: () => { + instanceInVForSlot = getCurrentInstance() + return template('content')() + }, + }, + ], + { + name: 'inVIf', + key: '1', + fn: () => { + instanceInVIfSlot = getCurrentInstance() + return template('content')() + }, + }, + ], ) }, }).render() - expect(instanceInSlot).toBe(instance) + expect(instanceInDefaultSlot).toBe(instance) + expect(instanceInVForSlot).toBe(instance) + expect(instanceInVIfSlot).toBe(instance) }) test.todo('should respect $stable flag', async () => { diff --git a/packages/runtime-vapor/src/componentRenderContext.ts b/packages/runtime-vapor/src/componentRenderContext.ts deleted file mode 100644 index e8051a360..000000000 --- a/packages/runtime-vapor/src/componentRenderContext.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - type ComponentInternalInstance, - currentInstance, - setCurrentInstance, -} from './component' - -export function withCtx<T extends (...args: any) => any>( - fn: T, - instance: ComponentInternalInstance | null = currentInstance, -): T { - if (!instance) return fn - - const fnWithCtx = ((...args: any[]) => { - const reset = setCurrentInstance(instance) - try { - return fn(...args) - } finally { - reset() - } - }) as T - return fnWithCtx -} diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 4eba7abf7..48ea4509c 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,6 +1,6 @@ -import { type IfAny, extend, isArray } from '@vue/shared' +import { type IfAny, isArray } from '@vue/shared' import { baseWatch } from '@vue/reactivity' -import type { ComponentInternalInstance } from './component' +import { type ComponentInternalInstance, setCurrentInstance } from './component' import type { Block } from './apiRender' import { createVaporPreScheduler } from './scheduler' @@ -29,7 +29,14 @@ export const initSlots = ( rawSlots: InternalSlots | null = null, dynamicSlots: DynamicSlots | null = null, ) => { - const slots: InternalSlots = extend({}, rawSlots) + const slots: InternalSlots = {} + + for (const key in rawSlots) { + const slot = rawSlots[key] + if (slot) { + slots[key] = withCtx(slot) + } + } if (dynamicSlots) { const dynamicSlotKeys: Record<string, true> = {} @@ -41,20 +48,22 @@ export const initSlots = ( // array of dynamic slot generated by <template v-for="..." #[...]> if (isArray(slot)) { for (let j = 0; j < slot.length; j++) { - slots[slot[j].name] = slot[j].fn + slots[slot[j].name] = withCtx(slot[j].fn) dynamicSlotKeys[slot[j].name] = true } } else if (slot) { // conditional single slot generated by <template v-if="..." #foo> - slots[slot.name] = slot.key - ? (...args: any[]) => { - const res = slot.fn(...args) - // attach branch key so each conditional branch is considered a - // different fragment - if (res) (res as any).key = slot.key - return res - } - : slot.fn + slots[slot.name] = withCtx( + slot.key + ? (...args: any[]) => { + const res = slot.fn(...args) + // attach branch key so each conditional branch is considered a + // different fragment + if (res) (res as any).key = slot.key + return res + } + : slot.fn, + ) dynamicSlotKeys[slot.name] = true } } @@ -77,4 +86,15 @@ export const initSlots = ( } instance.slots = slots + + function withCtx(fn: Slot): Slot { + return (...args: any[]) => { + const reset = setCurrentInstance(instance.parent!) + try { + return fn(...args) + } finally { + reset() + } + } + } } diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 81f190544..b15f4c461 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -50,7 +50,6 @@ export { type FunctionalComponent, type SetupFn, } from './component' -export { withCtx } from './componentRenderContext' export { renderEffect } from './renderEffect' export { watch,