router icon indicating copy to clipboard operation
router copied to clipboard

keep-alive component in nested route result in child route mounted twice

Open LiHDong opened this issue 4 years ago • 44 comments

Version

3.0.3

Reproduction link

https://codesandbox.io/s/nifty-roentgen-67uyr without vue router

Steps to reproduce

There is 5 files in the project. These files are App.vue, UserCenter/Index.vue, UserCenter/Push.vue, List/Index.vue, List/Detail.vue. Two Index.vue files are the child routes of App.vue, and I wrote keep-alive in App.vue. Push.vue and Detail.vue are child routes of two Index.vue, and I wrote keep-alive in two Index.vue to cache them.Here is the step to reproduce:

  1. First, open the sandbox link and open console, you will find the console print 'app loading';
  2. Second, click 'push page' link, console print 'user center loading... ' and 'push loading... ';
  3. Third, click 'detail page' link, console print 'list loading...' and 'detail loading...' twice;
  4. Forth, navigate back to previous push page, console print 'push loading' again.

What is expected?

In step 3, I just expect it print once; In step4, it's not supposed to print again;

What is actually happening?

In these circumstance, I found the child route mount twice;And about step 4 in my project, I found the cache did function, but it did mount again, which was confusing.

LiHDong avatar Dec 01 '20 03:12 LiHDong

It seems like a Vue-router bug.

edison1105 avatar Dec 01 '20 03:12 edison1105

It seems like a Vue-router bug.

I'm sorry that I cannot figure out where the problem is. Look forward to an avaible solution.Thanks!

LiHDong avatar Dec 01 '20 03:12 LiHDong

@LiHDong moved to vue-router repo for the moment

The problem comes from the nested router-view inside UserCenter: because it's kept alive, it reacts to route changes and tries to render with the new nested view. I will see if there is a way to prevent this.

posva avatar Dec 01 '20 08:12 posva

The problem is the same as https://github.com/vuejs/vue/issues/8819 which I don't know if it's expected or not. @yyx990803 is it normal for an inactive kept-alive component to keep rendering while inactive?

In the context of vue-router I tried internally avoiding rendering the router-view when the component is inactive, but it's too late, it still gets to mount the children once, resulting in mounting two Detail pages. So I tried not changing the route for nested router views but it turns out the onDeactivated hook triggers after computed based on the current route location, not allowing me to pass the old version of a route

// getting the global route or a route injected by a parent router-view
const injectedRoute = inject(routerViewLocationKey, inject( routeLocationKey));
        onDeactivated(() => {
            console.log('deactivated', depth)
        })
        const myRoute = computed(() => {
            console.log('computing myRoute')
            return (props.route || unref(injectedRoute))
        })
// providing the route to nested router-view
        provide(routerViewLocationKey, myRoute)

This prints computing myRoute and then deactivated. If it was the other way around, I could have cached the previous value of myRoute.

So far I don't see a way to handle this a part from manually deactivating any critical watcher with a variable that is toggled inside onDeactivated()

posva avatar Dec 01 '20 09:12 posva

Also ran into the mounted issue in nested routes when using keep-alive, which causes repeated calls to the database :( Didn't find a solution :(

[email protected] [email protected]

danitatt avatar Jan 17 '21 22:01 danitatt

Same problem here since from the beta version and to the v4.0.5

emiyalee1005 avatar Mar 20 '21 05:03 emiyalee1005

my component named SubModuleA.vue, its activated deactivated twice

https://codesandbox.io/s/cool-galois-f055n

edgexie avatar Jul 21 '21 09:07 edgexie

I also have a similar problem. Is there any solution.

x-255 avatar Aug 23 '21 07:08 x-255

I have also encountered this problem recently. Has this problem been solved?

XiaoRIGE avatar Sep 08 '21 02:09 XiaoRIGE

We have also encountered this issue. This issue will cause the component-level KeepAlive to fail if we are using Vue Router 4.x.

CNMathon avatar Sep 08 '21 02:09 CNMathon

Hello, Happy new year everyone !

This issue affects performance and kind of defeats the purpose of kept-alive router-views on vue 3. For example (extreme demo based on a real life observation):

Using keep alive: we see the double mount. Total time : 2.08s image

Not using keep alive: single mount. Total time : 1.11s image

We can see here that changing page is twice slower if router-view is wrapped in keep-alive tag. Worth noting that using keep-alive in vue 2 in the exact same scenario we get a total time of around 0.7s, 3x faster :( (I can share a reproduction if needed)


It also seems that the activated event is triggered on the component that gets deactivated. Reproduction here : https://github.com/nicolas-t/vue-3-keep-alive-lifecycle Demo here : https://vue-3-keep-alive-lifecycle.netlify.app/home/nested

Related topic : https://forum.vuejs.org/t/vue-3-keep-alive-lifecycle-issue/125549


I know it's been more than a year since this issue has been opened, but is there anything we can do to help ? It's blocking me and maybe others to migrate from vue 2 to vue 3 at the moment.

Thanks :)

nicolas-t avatar Jan 03 '22 15:01 nicolas-t

This is also true when using nested <Suspense> rather than <KeepAlive>: https://codesandbox.io/s/lively-mountain-o395nx. It feels like the cause is likely similar, but I'm happy to open a new issue if you think not.

danielroe avatar Feb 23 '22 09:02 danielroe

Hi, I have a similar issue with two nested QRouteTab component (from Quasar). Reproduction link: https://codesandbox.io/s/without-qtabpanel-m43nec If you click ont "test B" tab, the setup of test j is executed twice.

Console output of navigation between tabs: Test A (test i) -> Test B (test j)

[Quasar] Running SPA. 
##### setup -> index 
##### setup -> layout test A 
##### setup -> test i 
##### setup -> layout test B 
##### setup -> test j  
##### setup -> test j  

Also it seems that child component need to be mounted twice before to "keep-alive correctly". Example: if you comeback on test A the setup of "test i" is rerun. Then, you can navigate between both tabs without additional mount:

Console output of navigation between tabs: Test A (test i) -> test ii -> Test B (test j) -> Test A (test i) -> test ii -> test iii

[Quasar] Running SPA. 
##### setup -> index 
##### setup -> layout test A 
##### setup -> test I                          # first time
##### setup -> test ii                         # first time
##### setup -> layout test B 
##### setup -> test j 
##### setup -> test j  
##### setup -> test i                           # second time
##### setup -> test ii                          # second time
##### setup -> test iii
##### setup -> test iii

If you think this issue can be fixe with a cleaner code (good practice), I will thank you in advance for your help.

ericloud avatar Apr 08 '22 15:04 ericloud

I made a minimal reproducible example using [email protected] here: https://codesandbox.io/s/gifted-gagarin-g3ugux?file=/src/main.js It contains two child router-view, one uses keep-alive and the other one does not, and when navigating from /a/a to /b/a, the setup function in BA.vue logged twice.

rubick24 avatar May 13 '22 05:05 rubick24

In 2022, it seems the bug still exists. If keep-alive nested routes are used, the render is repeated

mooncoldrookie avatar May 30 '22 02:05 mooncoldrookie

记录

vue-router 4.0.15

子路由组件被两次初始化

  1. 第二次时,调用 inject 会报:[Vue warn]: injection "xxx" not found.

onActivated 和 onDeactivated 子页面进来和退出都会被调用

  1. 进来页面先调用 onDeactivated ,再调用 onActivated
  2. 退出页面先调用 onDeactivated ,再调用 onActivated,后再调用 onDeactivated

lin09 avatar Jun 07 '22 03:06 lin09

Deadalusmask's repro code shows one RouterView with a KeepAlive and one without. It seems to me that even if there are no KeepAlives in the nested RouterViews, the mounted and created hooks still fire twice in the child components: https://stackblitz.com/edit/vue-wvgvfa?file=src%2FApp.vue Navigate back and forth between Child1 and Child2 using the links and notice that created, mounted, and unmounted fire multiple times. For example, here's the result of navigating from Child1 to Child2.

Child2 created
Child2 created
Child1 unmounted
Child2 mounted
Child1 unmounted
Child2 mounted

I might take a stab at fixing this issue. I'm not familiar with the Vue Router code, so any hints on where to start would be helpful. Also, the documentation is (IMO) not clear in regard to using KeepAlive in RouterView's slot. Specifically, it's not clear to me if child components should be persisted in cache when the top-most RouterView uses KeepAlive, or if nested RouterViews should be required to use KeepAlive. My assumption is that the latter case is correct, but this part of the documentation seems contradictory:

When a component instance is removed from the DOM but is part of a component tree cached by <KeepAlive>, it goes into a deactivated state instead of being unmounted. When a component instance is inserted into the DOM as part of a cached tree, it is activated.

bbotto-pdga avatar Jun 21 '22 21:06 bbotto-pdga

测试使用 vue-router 版本:4.0.12

@lin09 我也因为 inject 的问题找到这儿,发现多层 router-view 的情况下,如果有 keep-alive 保活,那么子组件 setup 中会执行多次,执行的次数取决于同级的 keep-alive 的数量

测试时在 vue-devtools 中可以看到预料之外的层级关系,组件出现在不该出现的位置,导致存在三份组件,所以 setup 中的代码执行了三次

这是我的路由表,其中 app.vue、index.vue、letter.vue、number.vue、car.vue 中的 router-view 都使用了 keep-alive

<router-view v-slot="{Component}">
    <keep-alive>
        <component :is="Component"/>
    </keep-alive>
</router-view>
const routes = [
  {
    path: "/",
    redirect: {
      name: "index"
    }
  },
  {
    path: "/index",
    name: "index",
    component: () => import("/src/views/index.vue"),
    redirect: {
      name: "letter"
    },
    children: [
      {
        path: "letter",
        name: "letter",
        component: () => import("/src/views/letter/letter.vue"),
        redirect: {
          name: "letterA"
        },
        children: [
          {
            path: "letterA",
            name: "letterA",
            component: () => import("/src/views/letter/a.vue")
          }
        ]
      },
      {
        path: "number",
        name: "number",
        component: () => import("/src/views/number/number.vue"),
        redirect: {
          name: "numberOne"
        },
        children: [
          {
            path: "numberOne",
            name: "numberOne",
            component: () => import("/src/views/number/one.vue")
          }
        ]
      },
      {
        path: "car",
        name: "car",
        component: () => import("/src/views/car/car.vue"),
        redirect: {
          name: "carBmw"
        },
        children: [
          {
            path: "carBmw",
            name: "carBmw",
            component: () => import("/src/views/car/bmw.vue")
          }
        ]
      }
    ]
  }
];

show

imzhy avatar Jun 23 '22 09:06 imzhy

I use onActivated and onDeactivated to solve issue. Set a parameter isActiveusing these hooks

<router-view v-if="isActive" />

Yes it is ugly and adds unnecessary complexity to the code.

zortext avatar Jul 01 '22 08:07 zortext

is it possible to have this issue pinned?

cricketthomas avatar Jul 08 '22 21:07 cricketthomas

Continue to nested Keepalive in Keepalive can fix this bug app is the first level ,amd、cmd is the second,Their routing methods are as follows https://codesandbox.io/s/modest-thompson-klqcel image However, this will cause setup to execute multiple times

Spectature avatar Jul 12 '22 09:07 Spectature

I had the same problem 🥲🥲🥲 https://github.com/vuejs/core/issues/7057

pea-cake avatar Nov 08 '22 11:11 pea-cake