]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(keep-alive): fix keep-alive with scopeId/fallthrough attrs
authorEvan You <yyx990803@gmail.com>
Mon, 6 Jul 2020 22:12:16 +0000 (18:12 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 6 Jul 2020 22:17:40 +0000 (18:17 -0400)
fix #1511

packages/runtime-core/__tests__/components/KeepAlive.spec.ts
packages/runtime-core/src/components/KeepAlive.ts

index 3a3d85eeac23826944c0e6ef01d28c463f2ff238..f75bb039c4ec69f6e68e9cb6b38deee347b41523 100644 (file)
@@ -14,7 +14,8 @@ import {
   ComponentPublicInstance,
   Ref,
   cloneVNode,
-  provide
+  provide,
+  withScopeId
 } from '@vue/runtime-test'
 import { KeepAliveProps } from '../../src/components/KeepAlive'
 
@@ -655,4 +656,30 @@ describe('KeepAlive', () => {
     expect(spyMounted).toHaveBeenCalledTimes(3)
     expect(spyUnmounted).toHaveBeenCalledTimes(4)
   })
+
+  // #1513
+  test('should work with cloned root due to scopeId / fallthrough attrs', async () => {
+    const viewRef = ref('one')
+    const instanceRef = ref<any>(null)
+    const withId = withScopeId('foo')
+    const App = {
+      __scopeId: 'foo',
+      render: withId(() => {
+        return h(KeepAlive, null, {
+          default: () => h(views[viewRef.value], { ref: instanceRef })
+        })
+      })
+    }
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(`<div foo>one</div>`)
+    instanceRef.value.msg = 'changed'
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
+    viewRef.value = 'two'
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div foo>two</div>`)
+    viewRef.value = 'one'
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
+  })
 })
index 4cc7747b626d577968e90c4b849e3b439525ae7f..1ef589d74a61b06fadbefff4e2a8f811f13184ec 100644 (file)
@@ -9,7 +9,13 @@ import {
 } from '../component'
 import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
 import { warn } from '../warning'
-import { onBeforeUnmount, injectHook, onUnmounted } from '../apiLifecycle'
+import {
+  onBeforeUnmount,
+  injectHook,
+  onUnmounted,
+  onBeforeMount,
+  onBeforeUpdate
+} from '../apiLifecycle'
 import {
   isString,
   isArray,
@@ -173,6 +179,16 @@ const KeepAliveImpl = {
       }
     )
 
+    // cache sub tree in beforeMount/Update (i.e. right after the render)
+    let pendingCacheKey: CacheKey | null = null
+    const cacheSubtree = () => {
+      if (pendingCacheKey) {
+        cache.set(pendingCacheKey, instance.subTree)
+      }
+    }
+    onBeforeMount(cacheSubtree)
+    onBeforeUpdate(cacheSubtree)
+
     onBeforeUnmount(() => {
       cache.forEach(cached => {
         const { subTree, suspense } = instance
@@ -189,6 +205,8 @@ const KeepAliveImpl = {
     })
 
     return () => {
+      pendingCacheKey = null
+
       if (!slots.default) {
         return null
       }
@@ -227,7 +245,12 @@ const KeepAliveImpl = {
       if (vnode.el) {
         vnode = cloneVNode(vnode)
       }
-      cache.set(key, vnode)
+      // #1513 it's possible for the returned vnode to be cloned due to attr
+      // fallthrough or scopeId, so the vnode here may not be the final vnode
+      // that is mounted. Instead of caching it directly, we store the pending
+      // key and cache `instance.subTree` (the normalized vnode) in
+      // beforeMount/beforeUpdate hooks.
+      pendingCacheKey = key
 
       if (cachedVNode) {
         // copy over mounted state