diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap index 86e0b3d2fd5..8b536c50b97 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap @@ -5,13 +5,14 @@ exports[`compiler: v-memo transform > element v-for key expression prefixing + v export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => { + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, _i, _cached) => { const _memo = (_ctx.getLetter(data)) - if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached + if (_cached && _cached.el && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached const _item = (_openBlock(), _createElementBlock("span", { key: _ctx.getId(data) })) _item.memo = _memo + _item.cacheIndex = [0, _i] return _item }, _cache, 0), 128 /* KEYED_FRAGMENT */)) ])) @@ -53,11 +54,12 @@ exports[`compiler: v-memo transform > on template v-for 1`] = ` export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => { + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, _i, _cached) => { const _memo = ([x, y === _ctx.z]) - if (_cached && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached + if (_cached && _cached.el && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached const _item = (_openBlock(), _createElementBlock("span", { key: x }, "foobar")) _item.memo = _memo + _item.cacheIndex = [0, _i] return _item }, _cache, 0), 128 /* KEYED_FRAGMENT */)) ])) @@ -69,13 +71,14 @@ exports[`compiler: v-memo transform > on v-for 1`] = ` export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => { + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, _i, _cached) => { const _memo = ([x, y === _ctx.z]) - if (_cached && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached + if (_cached && _cached.el && _cached.key === x && _isMemoSame(_cached, _memo)) return _cached const _item = (_openBlock(), _createElementBlock("div", { key: x }, [ _createElementVNode("span", null, "foobar") ])) _item.memo = _memo + _item.cacheIndex = [0, _i] return _item }, _cache, 0), 128 /* KEYED_FRAGMENT */)) ])) diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 0dca0ba9ab4..664909443a5 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -218,6 +218,7 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform( } if (memo) { + forNode.parseResult.index = createSimpleExpression('_i') const loop = createFunctionExpression( createForLoopParams(forNode.parseResult, [ createSimpleExpression(`_cached`), @@ -226,7 +227,7 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform( loop.body = createBlockStatement([ createCompoundExpression([`const _memo = (`, memo.exp!, `)`]), createCompoundExpression([ - `if (_cached`, + `if (_cached && _cached.el`, ...(keyExp ? [` && _cached.key === `, keyExp] : []), ` && ${context.helperString( IS_MEMO_SAME, @@ -234,6 +235,9 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform( ]), createCompoundExpression([`const _item = `, childBlock as any]), createSimpleExpression(`_item.memo = _memo`), + createSimpleExpression( + `_item.cacheIndex = [${context.cached.length}, _i]`, + ), createSimpleExpression(`return _item`), ]) renderExp.arguments.push( diff --git a/packages/runtime-core/__tests__/helpers/withMemo.spec.ts b/packages/runtime-core/__tests__/helpers/withMemo.spec.ts index 32f89b1d8e9..cc447327e39 100644 --- a/packages/runtime-core/__tests__/helpers/withMemo.spec.ts +++ b/packages/runtime-core/__tests__/helpers/withMemo.spec.ts @@ -204,6 +204,32 @@ describe('v-memo', () => { ) }) + // #12708 + test('v-memo should work correctly when toggling v-if with v-for inside', async () => { + const [el, vm] = mount({ + template: ` + {{count}} + `, + data: () => ({ + show: true, + count: 0, + }), + }) + expect(el.innerHTML).toBe(`0`) + + vm.show = false + await nextTick() + expect(el.innerHTML).toBe(``) + + vm.show = true + await nextTick() + expect(el.innerHTML).toBe(`0`) + + vm.count++ + await nextTick() + expect(el.innerHTML).toBe(`1`) + }) + test('on v-for /w constant expression ', async () => { const [el, vm] = mount({ template: `
diff --git a/packages/runtime-core/src/helpers/renderList.ts b/packages/runtime-core/src/helpers/renderList.ts index bbcbcc13044..3b4a8c2fa40 100644 --- a/packages/runtime-core/src/helpers/renderList.ts +++ b/packages/runtime-core/src/helpers/renderList.ts @@ -78,7 +78,7 @@ export function renderList( ret[i] = renderItem( needsWrap ? toReactive(source[i]) : source[i], i, - undefined, + i, cached && cached[i], ) } @@ -88,12 +88,12 @@ export function renderList( } ret = new Array(source) for (let i = 0; i < source; i++) { - ret[i] = renderItem(i + 1, i, undefined, cached && cached[i]) + ret[i] = renderItem(i + 1, i, i, cached && cached[i]) } } else if (isObject(source)) { if (source[Symbol.iterator as any]) { ret = Array.from(source as Iterable, (item, i) => - renderItem(item, i, undefined, cached && cached[i]), + renderItem(item, i, i, cached && cached[i]), ) } else { const keys = Object.keys(source) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 90cc22f5470..ee3c2167d7f 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2097,7 +2097,15 @@ function baseCreateRenderer( // #6593 should clean memo cache when unmount if (cacheIndex != null) { - parentComponent!.renderCache[cacheIndex] = undefined + if (isArray(cacheIndex)) { + const [parentIndex, itemIndex] = cacheIndex + const parentCache = parentComponent!.renderCache[ + parentIndex + ]! as unknown as VNode[] + parentCache[itemIndex].el = null + } else { + parentComponent!.renderCache[cacheIndex] = undefined + } } if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index a8c5340cd1f..34aea8f79cb 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -243,8 +243,9 @@ export interface VNode< memo?: any[] /** * @internal index for cleaning v-memo cache + * cacheIndex will be an array when vnode in vFor + vMemo */ - cacheIndex?: number + cacheIndex?: number | number[] /** * @internal __COMPAT__ only */