Componentes asíncronos

23/27

Lectura

A medida que nuestra aplicación va creciendo, vamos buscando formas de mejorar su rendimiento. Tiene mucho sentido hacer Lazy Loading (o carga diferida) en las rutas, ya que solo abres una ruta por vez. Por lo tanto, no hace falta cargar todo el contenido de las rutas a la vez.

Por si acaso no tenías muy claro el concepto de Lazy Loading, podríamos decir es un patrón de diseño que consiste en retrasar la carga o inicialización de un objeto hasta el momento de su utilización.

Esta técnica ya la hemos utilizado con las rutas de nuestra app, en el router.js. Ahora vamos a ver cómo hacerlo con los componentes.

Actualmente, nuestros componentes de skills activas y pasivas se cargan de forma normal (síncrona). Lo que vamos a hacer ahora es cargar ambos componentes (habilidades activas y habilidades pasivas) con lazy loading, pero solo mostraremos uno, y podremos cambiar entre las activas y las pasivas con unos botones.

Por defecto, estaremos mostrando las habilidades activas, por lo que dicho componente se cargará inmediatamente cuando cargue la vista de /Hero. Sin embargo, el componente de habilidades pasivas no será cargado hasta que pulsemos el botón de Passive, que aún no hemos creado.

📗 Enlace a la documentación de Vue.js y componentes asíncronos: https://es.vuejs.org/v2/guide/components-dynamic-async.html#Componentes-asincronos

Hacer esto en Vue es muy fácil, gracias a los bundlers (como Webpack) que se encargan de separar el código y hacer que se utilice solo cuando es requerido.
Con un ejemplo en el código lo entenderás mejor.

El primer cambio que vamos a hacer es cargar los componentes de manera asíncrona. En vez de hacer el import que hacemos siempre vamos a hacer lo siguiente:

// /Hero/HeroSkills/Index.vue

// import ActiveSkills from './ActiveSkills'
// import PassiveSkills from './PassiveSkills'

export default {
  name: 'HeroSkills',
  components: {
    ActiveSkills: () => import('./ActiveSkills'),
    PassiveSkills: () => import('./PassiveSkills')
  },
  // ...
}

EL import habitual lo comentamos o lo borramos, porque ya no lo vamos a usar. Con esto ya estamos cargando nuestros componentes con lazy loading. Míralo aquí:

pv-1

Los archivos 18.js y 19.js son nuestros 2 componentes de habilidades. Esto lo sé porque los he abierto y he revisado su contenido, pero esta no es la forma adecuada. ¿Recuerdas como lo hicimos en las rutas 🤔?
En el import pusimos un comentario y esto al generar el chunk de código iba a tener el nombre que nosotros le asignamos.
En código se vería así:

export default {
  components: {
    ActiveSkills: () => import(/* webpackChunkName: "ActiveSkills" */'./ActiveSkills'),
    PassiveSkills: () => import(/* webpackChunkName: "PassiveSkills" */'./PassiveSkills')
  }
}

Si volvemos a la consola del navegador, ahora vemos esto:

pv-2

Mucho mejor así 👌.

Código completo del fichero /Hero/HeroSkills/Index.vue:

<template>
  <div class="skills-wrapper mt-5">
    <h2 class="font-diablo">Skills</h2>
    <hr class="bg-white">

    <ActiveSkills :skills="skills.active"/>
    <hr>
    <PassiveSkills :skills="skills.passive"/>

  </div>
</template>

<script>
export default {
  name: 'HeroSkills',
  components: {
    ActiveSkills: () => import(/* webpackChunkName: "ActiveSkills" */ './ActiveSkills'),
    PassiveSkills: () => import(/* webpackChunkName: "PassiveSkills" */ './PassiveSkills')
  },
  props: {
    skills: {
      required: true,
      type: Object
    }
  }
}
</script>

Hasta ahora lo único que hemos hecho es code splitting, es decir, trocear el código (en realidad lo hizo Webpack por nosotros: https://webpack.js.org/guides/code-splitting/). Y esto es así porque estamos mostrando los 2 componentes a la vez, por lo tanto se cargan los 2 a la vez y no hay lazy load.

Lo que puede ser mas interesante es tener 2 botones, cada uno asociado a un componente, y por defecto solo cargar las habilidades activas. Al pulsar el botón de las habilidades pasivas, cargar el contenido de este otro componente, es decir, hacer lazy loading (ahora sí). Vamos a ello:

Componentes dinámicos

Antes de poder hacer esto, necesitamos revisar los componentes dinámicos: https://es.vuejs.org/v2/guide/components.html#Componentes-dinamicos

En resumen, tenemos un nuevo tag con la propiedad is, que recibe como valor el nombre del componente que queremos cargar.

Podemos cargar varios componentes, por ejemplo:

import ComponentA from './ComponentA'
import ComponentB from './ComponentB'

Y luego usarlos así:

<component is="ComponentA"></component>
<component is="ComponentB"></component>

O así:

<component is="selectedComponent"></component>
computed: {
  selectedComponent () {
    return this.value === 'B' ? ComponentB : ComponentA
  }
}

Si value es igual a B, cargamos el componente B, en caso contrario, cargamos el componente A.

Esto es lo que vamos a hacer con los componentes de habilidades, usar el tag <component> y cargar el componente que necesitemos con is. ¡Vamos a ello!

Componentes dinámicos asíncronos 🤯

Empecemos cambiando el HTML de nuestro componente /Hero/HeroSkills/Index.vue:

<template>
  <div class="skills-wrapper mt-5">
    <h2 class="font-diablo">Skills</h2>
    <hr class="bg-white">

    <b-nav pills small>
      <b-nav-item :active="!isPassiveSkillsActive" @click="changeComponent('ActiveSkills')">Active</b-nav-item>
      <b-nav-item :active="isPassiveSkillsActive" @click="changeComponent('PassiveSkills')">Passive</b-nav-item>
    </b-nav>

    <component :is="activeComponent" :skills="componentProps"/>

    <!--
    <ActiveSkills :skills="skills.active"/>
    <hr>
    <PassiveSkills :skills="skills.passive"/>
    -->

  </div>
</template>

Vamos a crear una nueva variable en el data para controlar que componente está activo y por ende, cuál mostrar:

data () {
  return {
    activeComponent: 'ActiveSkills'
  }
}

Y unas computed properties:

computed: {
  /**
   * Dinamyc props for async dynamic components
   * @returns {String}
   */
  // Con esto estamos generando "props" dinámicas
  // Si el componente es ActiveSkills pasa como props las activas, si no, las pasivas
  componentProps () {
    return this.activeComponent === 'ActiveSkills' ? this.skills.active : this.skills.passive
  },
  // Nos dice si el componente "HabilidadesPasivas" está activo o no
  isPassiveSkillsActive () {
    return this.activeComponent === 'PassiveSkills'
  }
}

En esta parte hacemos un breve hincapié.
Cerramos los ojos y pensamos “¡Qué maravillosas que son las computed properties!” Esta es la potencia de las propiedades computadas.

La regla que yo sigo para saber cuando las tienes que usar es: “Siempre”. Abusa de las computadas y todo estará bien.

Hemos generado, a través de componentProps una prop (que pasamos de comp. padre a hijo) dinámica. Esto es necesario porque tenemos componentes dinámicos, por lo tanto las props no pueden ser estáticas como haciamos antes.

Ya por último, tenemos el método que hace que se cargue un componente u otro, cuando hacemos click:

methods: {
  changeComponent (component) {
    this.activeComponent = component
  }
}

El código completo se vería así:

<template>
  <div class="skills-wrapper mt-5">
    <h2 class="font-diablo">Skills</h2>
    <hr class="bg-white">

    <b-nav pills small>
      <b-nav-item :active="!isPassiveSkillsActive" @click="changeComponent('ActiveSkills')">Active</b-nav-item>
      <b-nav-item :active="isPassiveSkillsActive" @click="changeComponent('PassiveSkills')">Passive</b-nav-item>
    </b-nav>

    <component :is="activeComponent" :skills="componentProps"/>

  </div>
</template>

<script>

export default {
  name: 'HeroSkills',
  components: {
    // Dynamic Components
    ActiveSkills: () => import(/* webpackChunkName: "ActiveSkills" */'./ActiveSkills'),
    PassiveSkills: () => import(/* webpackChunkName: "PassiveSkills" */'./PassiveSkills')
  },
  props: {
    skills: {
      required: true,
      type: Object
    }
  },
  data () {
    return {
      activeComponent: 'ActiveSkills'
    }
  },
  computed: {
    /**
       * Dinamyc props for dynamic components
       * @returns {String}
       */
    componentProps () {
      return this.activeComponent === 'ActiveSkills' ? this.skills.active : this.skills.passive
    },
    isPassiveSkillsActive () {
      return this.activeComponent === 'PassiveSkills'
    }
  },
  methods: {
    changeComponent (component) {
      this.activeComponent = component
    }
  }
}
</script>

Este es el aspecto que tiene ahora nuestra app:

pv-3

Se ve en 2 columnas porque la app es responsive y así es como se ve en pantallas pequeñas. Ahora bien, si nos fijamos en la pestaña network de las herramientas para desarrolladores, vemos que solo ha cargado el componente de ActiveSkills. ¡Esto es genial!

Sin cerrar la pestaña de Network del navegador, hacemos click en el botón de Passive, veremos como se carga el otro componente.

pv-4

Hemos hecho lazy load de componentes con tan solo refactorizando un par de líneas. Ahora ya puedes aplicar este método a todos tus proyectos 👏.

Keep Alive

Sin embargo, tengo una (no tan buena) noticia que darte. Al hacer este cambio de pestañas, es decir, el cambio del componente ActiveSkills a PassiveSkills (o viceversa) múltiples veces, estamos haciendo otra vez peticiones de carga de imágenes. Mira lo que ha pasado al hacer el flujo Activas > Pasivas > Activas:

pv-5

Hemos cargado cada imagen varias veces, en este caso 3 veces. Cada vez que cambiamos de “pestaña”, el componente se destruye, por lo tanto las imágenes de las habilidades se tiene que volver a cargar para ser renderizadas.
Esto no es una buena práctica, pues estaríamos haciendo peticiones innecesarias para cargar contenido que antes teníamos cargado. ¡Esto no pasaba cuando teníamos componentes síncronos!

Para ver cómo se crea y se destruye el componente con esta técnica de lazy loading, podemos poner el código que tienes a continuación en el componente de /HeroSkills/PassiveSkills.vue:

created () {
  console.log('CREADO!')
},
destroyed () {
  console.log('DESTRUIDO!')
}

Ahora, prueba de nuevo a alternar entre componentes de habilidades activas y pasivas. Puedes ver como salen los logs en la consola del navegador.

pv-6

No problem. Vue tiene una solución para esto y se llama Keep Alive. Es una de las features que más me gusta, para lo complejo que podría llegar ser.

📗 Documentación oficial de Keep Alive: https://es.vuejs.org/v2/guide/components-dynamic-async.html#keep-alive-con-Componentes-Dinamicos & https://es.vuejs.org/v2/api/#keep-alive

Esta definición, sacada de la documentación oficial, me parece que es perfecta:

Keep Alive se utiliza principalmente para preservar el estado de los componentes o evitar la re-renderización.

Justo lo que nosotros queremos, que no se carguen de nuevo, en este caso, las imágenes.

Usarlo es tan fácil como envolver el componente que queremos mantener vivo con la etiqueta <keep-alive>:

<keep-alive>
  <component :is="activeComponent" :skills="componentProps"/>
</keep-alive>

Por lo tanto, usamos keep-alive en nuestro componente y probamos. El HTML del /Hero/HeroSkills/Index.vue debería estar así:

<template>
  <div class="skills-wrapper mt-5">
    <h2 class="font-diablo">Skills</h2>
    <hr class="bg-white">

    <b-nav pills small>
      <b-nav-item :active="!isPassiveSkillsActive" @click="changeComponent('ActiveSkills')">Active</b-nav-item>
      <b-nav-item :active="isPassiveSkillsActive" @click="changeComponent('PassiveSkills')">Passive</b-nav-item>
    </b-nav>

    <keep-alive>
      <component :is="activeComponent" :skills="componentProps"/>
    </keep-alive>

  </div>
</template>

En este tema hemos visto, Componentes Asíncronos, Componentes Dinámicos, Componentes Asíncronos Dinámicos con Props Dinámicas y Keep Alive. 😍

En el siguiente, vamos a terminar con esta vista, donde cargaremos los items (⚔️) de nuestro personaje.

Aportes 16

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

¿Qué te ha parecido? ¿Conocías keep-alive? ¿Lo has usado alguna vez? Para mí esta es de las mejores clases del curso.

Interesante lo del keep-alive, pero entonces me viene a la mente una duda:

Estamos encerrando TODO el componente en un keep alive, ¿Eso significa que no se volverá a cargar nunca más? (Hasta que recarguemos la página obviamente)

Si ese es el caso, entonces no podría refrescar los datos cada vez que cambie de componente ya que sus hooks no se ejecutarían porque ya estarían cargados, y algo que me gusta de Vue es que al volver a cargar el componente puedo refrescar sus datos

Justo cuando pensaba que este curso y Vue no me podrían sorprender más, aparece esta súper clase! Siento que el profe me ha transmitido mucho conocimiento y pasión por Vue. Si lees, gracias Jorge Baumann!

Con Vue3 tuve varios errores y no entendía el porque, luego revisé la documentación y para componentes asíncronos se cargan así:

import { defineAsyncComponent } from 'vue'
.
.
.
components: {
    ActiveSkills: defineAsyncComponent(() =>
      import(/* webpackChunkName: "ActiveSkills" */ './ActiveSkills'),
    ),
    PassiveSkills: defineAsyncComponent(() =>
      import(/* webpackChunkName: "PassiveSkills" */ './PassiveSkills'),
    ),
  },

Con eso ya funciona como el ejemplo de la clase.

Lo que más amo de Vue es su simpleza para lograr cosas complejas, aprendí muchas cosas super útiles en esta clase.

Que interesante va todo el curso🤩 estoy ansiosa por terminar

Excelente clase del curso, voy a investigar más sobre keep alive porque tengo varias dudas sobre si vuelve a hacer peticiones o no al server cuando necesite refrescar los datos.

Cuando realize el ejercicio note que no se piden de nuevo las imagenes por que quedan en cache, lo probe con Mozilla y Chrome aunque me parecio muy interesate keep alive.

Esta clase estuvo super emocionante !

No tenia idea del poder que Lazy Loading con un Keep-alive nos podría brindar en el rendimiento de nuestra aplicación con Vue, genial 🤯

Genial 🤯

Ya me habia tocado trabajar con Keep-Alive, tenia un componente con un render de un Mapbox que se podia ocultar, y recargaba todo el componente cuando lo mostraba de nuevo, muy pesado estar solicitando mapas cada momento!
Gran forma de produndizarlo…👍👍

Pregunta acerca del juego, Los Skills pasivos no tiene Runas? trato de a los Skills pasivos pintarles las rrunas con su nombre y me sale runas undefined

Siempre que me acerco a Vue.js no deja de sorprenderme con alguna novedad, definitivamente esta fue la gran sorpresa, no tenía ni idea de esta potente característica la cual asumo que con React por ejemplo es posible pero no de una manera tan sencilla.
Saludos compañeros y profesor.

Aunque no he probado React, creo que fue una buena elección elegir Vue, al usarlo con pug, todo queda sencillo, legible, y de rápido desarrollo. Se siente lo mismo que trabajar con Python en el Backend. Un problema? estas sencillas lineas de código y pum, todo listo…💪🏽

Sonará extraño, pero llevo haciendo este curso con React jajaja. En mi investigación para hacer todo lo de esta clase en React he aprendido muchísimo y al parecer no existe algo como keep-alive por lo que opte por simplemente hacer uso de css para ocultar los componentes y de esta manera no cargar las imágenes cada vez que las mostremos, lo malo de hacerlo de esta manera es que ambos componentes se cargan en simultaneo