Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do You Get Vue.js Components to be Recycled/Persist Between Router Changes?

I am using Vue.js 2.5.x and Nuxt 1.4. I believe this is a question related to vue-router.

I have the same component on two different Nuxt pages that I want to preserve in the layout (not just in memory) when navigating between the pages.

To put this in terms of lifecycle events, some components are added and removed from a layout and trigger a whole lifecycle of being created, mounted then unmounted, destroyed. I understand how keep-alive works to avoid the created/destroyed overhead of that process for components we expect to be re-mounted in the layout, and that isn't the question here.

In contrast, simple components seem to not be unmounted at all when a route changes, as though Vue somehow understands that these components will appear the same in two different layouts so not only does it not destroy/create them... it leaves them in the layout and doesn't even unmount them.

I am trying to get a better understanding of the conditions that allow a current page's components to remain mounted when navigating between routes. Many of the discussions I’ve found are of the nature of “Why isn’t my component refreshing?” when a route change happens, but I actually have the opposite problem: I want to preserve a component and its state, but the component is getting destroyed. I have played with setting 'key' explicitly to a specific shared value (the opposite advice usually given to insure components DO unmount) but there seems to be something deeper than this.

Again, for clarity, I'm not referring to "keep-alive" trying to hang onto components in memory that are temporarily removed from a layout. What I am observing and trying to understand here seems to be a different behavior, where some part of Vue recognizes components as being identical between two layouts and it optimizes away the destroy and (re)create of such components. This is a huge optimization, but one whose behavior doesn't seem to be discussed or documented anywhere I can find.

I have a Nuxt layout that is conceptually like this…

default.vue:

<template>
  <div>
    <my-marvelous-header-component />
    <nuxt/>
    <my-also-marvelous-footer-component />
  </div>
</template>

…and I have a couple of Nuxt pages that look like this…

page-a.vue:

<template>
  <section id=mainContent>
    <wonderful-component id="wonder1" :key=321 />
    <complex-component-with-children :key=123 />
  </section>
</template>

page-b.vue:

<template>
  <section id=mainContent>
    <wonderful-component id="wonder1" :key=321 />
    <complex-component-with-children :key=123 />
  </section>
</template>

You’ll note that both pages have exactly the same components, and I’ve tried to identify them uniquely with key properties in an effort to communicate to Vue that these are the same creatures at render time.

When I navigate between these pages with this.$router.push(), my header and footer components survive the route navigation unmolested (I verified this by putting some console output in the lifecycle hooks), however both wonderful and complex components are destroyed and then recreated.

Both of the components I’m trying to recycle have a number of dynamically created child components inside of them, so the state of the vdom is going to be considerably different from the raw starting conditions of the initial page. Components like wonderful or complex don’t have any properties or any other data passed from the template… They are exactly as shown in the layout above. I’ve experimented with giving them a unique ID or key value that is shared across the templates (as well as nothing at all), but no matter what I have tried, the router push causes these components to destroy and re-render.

Trivial components like my header and footer recycle fine, I just want to make my more complex components behave likewise.

So my question centrally is, what allows or prevents recycling of components? What is examined to determine if a component can be recycled? Is there a way to signal that a component should be preserved/recycled between route changes? If not, what do I have to keep outside of the component to make it appear as a candidate for sticking around across transitions?

I had thought that the “key” property was the magic here, but that doesn’t seem to work and unfortunately browsing the Vue.js source for this word reveals that it is used quite extensively to name parameters and local variables… I believe there’s a function within Vue called “patch()” that deals with updating at least the vdom consistently with old and new components, but frankly the logic in there exceeds my present Vue knowledge. If someone with some better knowledge of the Vue internals had insights on what parts of the code might help clarify my thinking, I'd be keen to dive through this again.

I feel like I’ve been chasing my tail on this for several days. Any thoughts or insights are much appreciated.

like image 815
James Scheller Avatar asked May 30 '18 20:05

James Scheller


2 Answers

(Not sure if these explanations would be enough for you, but I'm just going to attempt to answer it anyway)

Rather than recycling, the general term used in Vue is called keep-alive. This is the term you would want to use when you are researching this.

For Nuxt, keep-alive doesn't seem to be reliable yet. I would suggest you to keep all data in Vuex and render your elements based on the data in Vuex.

what allows or prevents recycling of components? What is examined to determine if a component can be recycled?

It's useful to think in terms of mounting and unmounting here. By default, a component is always destroyed when unmounting. (Unless keep-alive is used.)

In this example,

<my-marvelous-header-component />
<nuxt/>
<my-also-marvelous-footer-component />

<nuxt/> is where <router-view></router-view> of Vue router is located in. On route changes, only components inside router-view would be mounted/unmounted. Therefore, on route change, <my-marvelous-header-component /> and <my-also-marvelous-footer-component /> would remain unchanged and would not be mounted/unmounted.

Is there a way to signal that a component should be preserved/recycled between route changes?

In a typical (Not Nuxt) Vue project, it's controlled by applying <keep-alive> on <router-view>.

This is a pretty good example: https://jsfiddle.net/Linusborg/L613xva0/4/

<div id="app">
  ...
  <keep-alive include="foo">
    <router-view></router-view>
  </keep-alive>
</div>

But in Nuxt, <router-view> is automatically generated and you won't be able to apply <keep-alive> to it like in a normal Vue project.

(Again) For Nuxt, keep-alive doesn't seem to be reliable yet. I would suggest you to keep all data in Vuex and render your elements based on the data in Vuex.

like image 71
Jacob Goh Avatar answered Oct 16 '22 13:10

Jacob Goh


inertiajs does this with vue components

create a vue template for the layout

Layout.vue:

<template>
    <div>
        <!-- Your Layout code -->
    </div>
</template>

SomePage.vue:

<template>
    <div>
        <!-- Your specific page's code -->
    </div>
</template>

<script>
import Layout from 'path/to/Layout'

    export default {
        layout: Layout,
    }
</script>

SomePage will come back wrapped in Layout

there's a lot more one can do with this framework such as default layouts, layouts for pages in certain directories, etc.

and this layout structure will ensure that between each page call the layout is persisted and not destroyed and rebuilt per page call so you can save scroll position, continue playing audio when a user navigates away, etc. all this can be found here

you can also use a render function to get the layout to display from the export statement, check out the PingCRM git repo for more examples and maybe consider this wonderful series on Inertia.js, Vue, and Laravel by laracasts

like image 1
Liam O'Toole Avatar answered Oct 16 '22 13:10

Liam O'Toole