Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f96ba5f

Browse filesBrowse files
committed
fix(runtime-vapor): use computed to cache the result of dynamic slot function to avoid redundant calls.
1 parent e499d56 commit f96ba5f
Copy full SHA for f96ba5f

File tree

Expand file treeCollapse file tree

2 files changed

+199
-38
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+199
-38
lines changed
Open diff view settings
Collapse file

‎packages/runtime-vapor/__tests__/componentSlots.spec.ts‎

Copy file name to clipboardExpand all lines: packages/runtime-vapor/__tests__/componentSlots.spec.ts
+180-35Lines changed: 180 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -103,41 +103,6 @@ describe('component: slots', () => {
103103
expect(instance.slots).toHaveProperty('two')
104104
})
105105

106-
test('should work with createFlorSlots', async () => {
107-
const loop = ref([1, 2, 3])
108-
109-
let instance: any
110-
const Child = () => {
111-
instance = currentInstance
112-
return template('child')()
113-
}
114-
115-
const { render } = define({
116-
setup() {
117-
return createComponent(Child, null, {
118-
$: [
119-
() =>
120-
createForSlots(loop.value, (item, i) => ({
121-
name: item,
122-
fn: () => template(item + i)(),
123-
})),
124-
],
125-
})
126-
},
127-
})
128-
render()
129-
130-
expect(instance.slots).toHaveProperty('1')
131-
expect(instance.slots).toHaveProperty('2')
132-
expect(instance.slots).toHaveProperty('3')
133-
loop.value.push(4)
134-
await nextTick()
135-
expect(instance.slots).toHaveProperty('4')
136-
loop.value.shift()
137-
await nextTick()
138-
expect(instance.slots).not.toHaveProperty('1')
139-
})
140-
141106
// passes but no warning for slot invocation in vapor currently
142107
test.todo('should not warn when mounting another app in setup', () => {
143108
const Comp = defineVaporComponent({
@@ -1936,4 +1901,184 @@ describe('component: slots', () => {
19361901
})
19371902
})
19381903
})
1904+
1905+
describe('createForSlots', () => {
1906+
test('should work', async () => {
1907+
const loop = ref([1, 2, 3])
1908+
1909+
let instance: any
1910+
const Child = () => {
1911+
instance = currentInstance
1912+
return template('child')()
1913+
}
1914+
1915+
const { render } = define({
1916+
setup() {
1917+
return createComponent(Child, null, {
1918+
$: [
1919+
() =>
1920+
createForSlots(loop.value, (item, i) => ({
1921+
name: item,
1922+
fn: () => template(item + i)(),
1923+
})),
1924+
],
1925+
})
1926+
},
1927+
})
1928+
render()
1929+
1930+
expect(instance.slots).toHaveProperty('1')
1931+
expect(instance.slots).toHaveProperty('2')
1932+
expect(instance.slots).toHaveProperty('3')
1933+
loop.value.push(4)
1934+
await nextTick()
1935+
expect(instance.slots).toHaveProperty('4')
1936+
loop.value.shift()
1937+
await nextTick()
1938+
expect(instance.slots).not.toHaveProperty('1')
1939+
})
1940+
1941+
test('should cache dynamic slot source result', async () => {
1942+
const items = ref([1, 2, 3])
1943+
let callCount = 0
1944+
1945+
const getItems = () => {
1946+
callCount++
1947+
return items.value
1948+
}
1949+
1950+
let instance: any
1951+
const Child = defineVaporComponent(() => {
1952+
instance = currentInstance
1953+
// Create multiple slots to trigger multiple getSlot calls
1954+
const n1 = template('<div></div>')()
1955+
const n2 = template('<div></div>')()
1956+
const n3 = template('<div></div>')()
1957+
insert(createSlot('slot1'), n1 as any as ParentNode)
1958+
insert(createSlot('slot2'), n2 as any as ParentNode)
1959+
insert(createSlot('slot3'), n3 as any as ParentNode)
1960+
return [n1, n2, n3]
1961+
})
1962+
1963+
define({
1964+
setup() {
1965+
return createComponent(Child, null, {
1966+
$: [
1967+
() =>
1968+
createForSlots(getItems(), (item, i) => ({
1969+
name: 'slot' + item,
1970+
fn: () => template(String(item))(),
1971+
})),
1972+
],
1973+
})
1974+
},
1975+
}).render()
1976+
1977+
// getItems should only be called once
1978+
expect(callCount).toBe(1)
1979+
1980+
expect(instance.slots).toHaveProperty('slot1')
1981+
expect(instance.slots).toHaveProperty('slot2')
1982+
expect(instance.slots).toHaveProperty('slot3')
1983+
})
1984+
1985+
test('should update when source changes', async () => {
1986+
const items = ref([1, 2])
1987+
let callCount = 0
1988+
1989+
const getItems = () => {
1990+
callCount++
1991+
return items.value
1992+
}
1993+
1994+
let instance: any
1995+
const Child = defineVaporComponent(() => {
1996+
instance = currentInstance
1997+
const n1 = template('<div></div>')()
1998+
const n2 = template('<div></div>')()
1999+
const n3 = template('<div></div>')()
2000+
insert(createSlot('slot1'), n1 as any as ParentNode)
2001+
insert(createSlot('slot2'), n2 as any as ParentNode)
2002+
insert(createSlot('slot3'), n3 as any as ParentNode)
2003+
return [n1, n2, n3]
2004+
})
2005+
2006+
define({
2007+
setup() {
2008+
return createComponent(Child, null, {
2009+
$: [
2010+
() =>
2011+
createForSlots(getItems(), (item, i) => ({
2012+
name: 'slot' + item,
2013+
fn: () => template(String(item))(),
2014+
})),
2015+
],
2016+
})
2017+
},
2018+
}).render()
2019+
2020+
expect(callCount).toBe(1)
2021+
expect(instance.slots).toHaveProperty('slot1')
2022+
expect(instance.slots).toHaveProperty('slot2')
2023+
expect(instance.slots).not.toHaveProperty('slot3')
2024+
2025+
// Update items
2026+
items.value.push(3)
2027+
await nextTick()
2028+
2029+
// Should be called again after source changes
2030+
expect(callCount).toBe(2)
2031+
expect(instance.slots).toHaveProperty('slot1')
2032+
expect(instance.slots).toHaveProperty('slot2')
2033+
expect(instance.slots).toHaveProperty('slot3')
2034+
})
2035+
2036+
test('should render slots correctly with caching', async () => {
2037+
const items = ref([1, 2, 3, 4, 5])
2038+
2039+
const Child = defineVaporComponent(() => {
2040+
const containers: any[] = []
2041+
for (let i = 1; i <= 5; i++) {
2042+
const n = template('<div></div>')()
2043+
insert(createSlot('slot' + i), n as any as ParentNode)
2044+
containers.push(n)
2045+
}
2046+
return containers
2047+
})
2048+
2049+
const { host } = define({
2050+
setup() {
2051+
return createComponent(Child, null, {
2052+
$: [
2053+
() =>
2054+
createForSlots(items.value, item => ({
2055+
name: 'slot' + item,
2056+
fn: () => template('content' + item)(),
2057+
})),
2058+
],
2059+
})
2060+
},
2061+
}).render()
2062+
2063+
expect(host.innerHTML).toBe(
2064+
'<div>content1<!--slot--></div>' +
2065+
'<div>content2<!--slot--></div>' +
2066+
'<div>content3<!--slot--></div>' +
2067+
'<div>content4<!--slot--></div>' +
2068+
'<div>content5<!--slot--></div>',
2069+
)
2070+
2071+
// Update items
2072+
items.value = [2, 4]
2073+
await nextTick()
2074+
2075+
expect(host.innerHTML).toBe(
2076+
'<div><!--slot--></div>' +
2077+
'<div>content2<!--slot--></div>' +
2078+
'<div><!--slot--></div>' +
2079+
'<div>content4<!--slot--></div>' +
2080+
'<div><!--slot--></div>',
2081+
)
2082+
})
2083+
})
19392084
})
Collapse file

‎packages/runtime-vapor/src/componentSlots.ts‎

Copy file name to clipboardExpand all lines: packages/runtime-vapor/src/componentSlots.ts
+19-3Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
2+
import { type ComputedRef, computed } from '@vue/reactivity'
23
import { type Block, type BlockFn, insert, setScopeId } from './block'
34
import { rawPropsProxyHandlers } from './componentProps'
45
import {
@@ -51,9 +52,24 @@ export type StaticSlots = Record<string, VaporSlot>
5152

5253
export type VaporSlot = BlockFn
5354
export type DynamicSlot = { name: string; fn: VaporSlot }
54-
export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
55+
export type DynamicSlotFn = (() => DynamicSlot | DynamicSlot[]) & {
56+
_cache?: ComputedRef<DynamicSlot | DynamicSlot[]>
57+
}
5558
export type DynamicSlotSource = StaticSlots | DynamicSlotFn
5659

60+
/**
61+
* Get cached result of a DynamicSlotFn.
62+
* Uses computed to cache the result and avoid redundant calls.
63+
*/
64+
function resolveDynamicSlot(
65+
source: DynamicSlotFn,
66+
): DynamicSlot | DynamicSlot[] {
67+
if (!source._cache) {
68+
source._cache = computed(source)
69+
}
70+
return source._cache.value
71+
}
72+
5773
export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
5874
get: getSlot,
5975
has: (target, key: string) => !!getSlot(target, key),
@@ -74,7 +90,7 @@ export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
7490
keys = keys.filter(k => k !== '$')
7591
for (const source of dynamicSources) {
7692
if (isFunction(source)) {
77-
const slot = source()
93+
const slot = resolveDynamicSlot(source)
7894
if (isArray(slot)) {
7995
for (const s of slot) keys.push(String(s.name))
8096
} else {
@@ -103,7 +119,7 @@ export function getSlot(
103119
while (i--) {
104120
source = dynamicSources[i]
105121
if (isFunction(source)) {
106-
const slot = source()
122+
const slot = resolveDynamicSlot(source)
107123
if (slot) {
108124
if (isArray(slot)) {
109125
for (const s of slot) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.