DÍA 11 / 2020

¿Está a la altura del anime? One Piece - Vue 3

¿Con ganas de conocer que nos trae la nueva versión de Vue? En este artículo hacemos un repaso sobre ella; Composition API, nuevas funcionalidades, cambios con respecto a la versión anterior y mi humilde opinión sobre si merece la pena actualizar proyectos. “Leelo, sé que te va a encantar”.


— La versión 3 de Vue est...—

— ¡¿Pero y qué es Vue?! —grita alguien desde el fondo.

— A ver, lo intento explicar breve pero completo: Es un Framework Open Source JavaScript para construir interfaces de usuario. Se caracteriza principalmente por usar una arquitectura de componentes, por el uso de vistas reactivas, por su baja curva de aprendizaje y por poder tener todo el código de un componente en un solo fichero. —

— Pués prefiero React... —dice la misma persona en voz baja.

— ...bueno, ahora sí,... —

La versión 3 de Vue está en una versión estable, por lo que está lista para migrar nuestros proyectos en Vue 2.x y para la creación de nuevos proyectos. Todas las librerías y herramientas del equipo de Vue ya son compatibles con esta versión, aunque aún nos las podemos encontrar en un estado de beta. En la documentación oficial de Vue tenemos el estado actual de estas librerías.

Debido a que estas librerías están en beta, se utiliza la etiqueta next en NPM para la instalación de los paquetes, por lo que npm install vue instalará la versión Vue 2.x y npm install vue@next instalará Vue 3. El equipo de Vue tiene planeado dejar las versiones definitivas y fijas a finales de este (rarísimo) año.

Una vez hecha la introducción reglamentaria, ¡a zarpar!

Composition API 📖

Si buscas información sobre Vue 3, encontrarás por todos sitios el término "Composition API", ¡¿ pero esto que eeees ?!

Básicamente es un nuevo conjunto de APIs destinadas a mejorar los puntos débiles de aplicaciones a gran escala ya que facilita la organización y reutilización del código, pudiendo reutilizar lógica entre componentes usando simples funciones. Además mejora la compatibilidad con TypeScript. Aunque es una herramienta incorporada en la nueva versión, es muy interesante revisarla aunque tu aplicación esté desarrollada con Vue 2.x ya que el equipo la ha puesto accesible mediante el plugin @vue/composition-api.

Tengamos en cuenta que esta es una nueva forma de escribir componentes, pero toda la sintaxis de la versión anterior sigue siendo válida.

Veamos las principales funcionalidades de esta API.

Setup

Es una nueva propiedad (y de las más importantes) en los componentes. Se lanza antes de que el componente esté creado pero justo después de haber calculado el valor de las props, es decir, la instancia del componente aún no se ha creado, por lo que no se tiene acceso a data, computed o methods.

Debe ser una función que acepte props y context.

setup (props, context) {
      context.attrs
      context.slots
      context.parent
      context.root
      context.emit
    }

Y lo que devuelve será accesible en todo el componente (computed, methods, funciones del ciclo de vida, template, etc.). Puede parecer que esto añada más verbosidad innecesaria pero nos ayuda a tener más control de qué se expone a nuestra template:

// src/components/OnePiece.vue

export default {
  props: {
    character: {
      type: String,
      default: 'Luffy'
    }
  },
  setup(props, context) {
    console.log(props) // { character: '' }

    return {}
  }
  // ...
}

Si has utilizado ya Vue 2.x, quizás te recuerde un poco a lo que encontramos con la propiedad data(), en cambio en setup() existen tres diferencias principales:

  • Además de poder devolver datos para ser mostrados en la template, puede devolver el renderizado del propio componente con la función h.
    import { h } from 'vue'

    export default {
      props: {
        character: {
          type: String,
          default: 'Luffy'
        }
      },
      setup() {
        return () => h('div', [props.character])
      }
      // ...
    }
  • Al no haber aún inicializado el componente, no tenemos acceso al objeto this.
  • Podemos acceder a todos los eventos del ciclo de vida (lo vemos a continuación).
    import { onMounted, onUpdated, onUnmounted } from '@vue/composition-api'

    export default {
      setup() {

        onMounted(() => {
          console.log('Component mounted!')
        })
        onUpdated(() => {
          console.log('Component updated!')
        })
        onUnmounted(() => {
          console.log('Component unmounted!')
        })
      }
    }

Ciclos de vida

Los eventos del ciclo de vida de un componente que conocimos en la versión 2.x siguen utilizándose aunque podemos cambiarlos para mejorar la convención y la nomenclatura sea más homogénea a como se llaman el resto de métodos:

beforeDestroybeforeUnmount
destroyedunmounted

Además, podemos utilizar estos eventos dentro de la función setup() concatenando como prefijo "on":

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdate
  • onBeforeUnmount
  • onUnmonted
  • onActivated
  • onDeactivate
  • onErrorCaptured
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured } from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      // ...
    })
    onMounted(() => {
      // ...
    })
    onBeforeUpdate(() => {
      // ...
    })
    onUpdated(() => {
      // ...
    })
    onBeforeUnmount(() => {
      // ...
    })
    onUnmounted(() => {
      // ...
    })
    onActivated(() => {
      // ...
    })
    onDeactivated(() => {
      // ...
    })
    onErrorCaptured(() => {
      // ...
    })
  }
}

Además, han añadido 2 nuevos métodos:

  • (on)renderTracked: Se llama cuando se accede por primera vez a una dependencia reactiva en la función de render.
  • (on)renderTriggered: Se invoca cuando se produce un nuevo render del componente, permitiendo conocer qué lo disparó.

Ref

Si has trabajado con Vue 2.x es posible que hayas utilizado las referencias ($refs), un objeto en el que se almacenan los elementos DOM que tengan el atributo ref en la template. En esta nueva versión, encontramos ref como un método de la Composition API que crea un objeto reactivo, por lo tanto podemos declarar objetos reactivos que no están asociados a un componente.

import { ref } from 'vue'

  export default {
    setup() {
      const seasson = ref(0)

      // expone a la template
      return {
        seasson
      }
    }
  }

¿Qué hay de nuevo? 🥕

Además de la Composition API aún hay algunas sorpresas más:

Teleport

Buscando más reutilización y organización del código, llega esta nueva funcionalidad. Este nuevo elemento nos permite mover plantillas HTML a distintas partes del DOM. Por ejemplo, podríamos mover una template (o parte de ella) que esté dentro de la etiqueta de nuestro componente y moverla donde queramos del index.html (donde se inyecta nuestra aplicación), tan solo indicando dónde debe colocarse con el atributo to.

imagen_teleport_1
imagen_teleport_2

Suspense

Con esta nueva etiqueta podemos mostrar un contenido concreto mientras termina la llamada asíncrona en nuestro método setup. Veamos un ejemplo:

<template>
 <div>
  {{ result }}
 </div>
</template>
export default {
  name: 'MyComponent',
   async setup () {
    const result = await fetch(URL)
     return {
       result
    }
  }
}

En el componente padre de MyComponent se añade el siguiente HTML.

<Suspense>
  <template #default>
    <MyComponent />
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>

Se mostrará el mensaje "Loading..." hasta que no haya terminado la llamada asíncrona dentro de setup.

Custom Events

Los nombre de los events lanzados deben coincidir exactamente con el nombre del evento escuchado en el componente padre, es decir, es sensible a mayúsculas y minúsculas, por ejemplo:

this.$emit('selectCharacter')

En el componente que está escuchando este event, no se lanzaría nada si el nombre del event se llamase:

<list-characters @select-character="doSomething"></list-characters>

En estos casos, lo mejor es usar la nomenclatura kebab-case, es decir, en nuestro ejemplo sería dejarlo de la forma select-character.

En esta nueva versión, podemos definir una lista de los nombre de los events en las propiedades del componente (de esta forma queda más claro que eventos se usan en un componente):

export default {
  props: {
    character: {
      type: String,
      default: 'Luffy'
    }
  },
  emits: ['select-character', 'delete-character']
}

Se pueden utilizar eventos nativos como click, pero hará que reemplace el comportamiento original del mismo. Además, podemos establecer validaciones para indicar si el evento es válido o no. En el siguiente caso, el evento delete-character solo será válido (y por tanto se emitirá al componente padre) si el id y name no son nulos.

export default {
  props: {
    character: {
       type: String,
       default: ‘Luffy’
    }
  },
  emits: {
    ‘select-character’: null, // No comprueba y por lo tanto es válido,
    ‘delete-character’: ({id, name}) => id && name
  }
}

Múltiples elementos Root

¿Te da rabia añadir elementos <div> como wrapper en tus componentes para contener a dos elementos que pondrías sueltos? Por fin han añadido la solución. Si vienes de React seguramente estés usando continuamente la etiqueta como wrapper del contenido HTML. Ahora es posible tener más de un elemento HTML en la raíz de la del componente.

<template>
    <h1>One Piece</h1>
    <h2>Vue 3</h2>
</template>

TypeScript Support

Como sabrás, TypeScript puede ayudar a prevenir muchos errores en tiempo de ejecución a medida que crecen las aplicaciones (como ya he dicho antes, otra ventaja para aplicaciones de gran tamaño). El código base de Vue 3 está escrito en TypeScript por lo que tenemos soporte nativo y no necesitamos ayuda de librerías externas.

Configuración recomendada:

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // this enables stricter inference for data properties on `this`
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}

Es un añadido por lo que no es obligatorio usar TypeScript en tu proyecto Vue, si quieres usarlo, seleccionalo en la creación a pasos del CLI o simplemente añadiendo la librería con vue add typescript. Si lo usas, recuerda añadir la anotación lang=”ts” en las etiquetas <script> de los componentes.

Performance

El equipo de Vue no solo se ha encargado de desarrollar nuevas funcionalidades, minimizar el tamaño de la librería o mejorar el soporte para TypeScript, ¡además han mejorado los tiempos de carga del framework!, si os parecía ya rápido, ahora podemos flipar un poco comparando los tiempos en esta Spreadsheet oficial del equipo.

Principales cambios 🔨

Crear una aplicación

Antes importábamos Vue y creábamos una instancia de Vue para utilizar .mount() y crear la aplicación. Ahora debemos importarnos el método createApp indicando el contenedor en el que se cargará. De esta forma, podemos añadir APIs (por ejemplo Vuex o Vue I18n) que se aplican únicamente sobre la instancia de la aplicación específica y no sobre toda la aplicación.

import { createApp } from 'vue'
import router from './router'
import { i18n } from './i18n'
import App from './App.vue'

const appA = createApp(App)
appA.use(router)
appA.use(i18n)
appA.mount('#appA')

const appB = createApp(App)
appB.mount('#appB')

Al llamar a createApp se devuelve una instancia de la aplicación. Esta instancia proporciona un contexto de aplicación.

Tree-shacking

Ciertas API globales expuestas directamente en el objeto Vue, ya no son accesibles. Por ejemplo, la función Vue.$nextTick (una función que aplaza la ejecución del callback que le pasamos hasta después del próximo ciclo de actualización del DOM) no podemos invocarla de esta forma o con this.$nextTick. Ahora tenemos que importarlo desde el módulo vue para mejorar así el tree-shaking.

import { nexTickt } from 'vue'

Las APIs afectadas son:

  • Vue.nextTick
  • Vue.observable (reemplazado por Vue.reactive).
  • Vue.version
  • Vue.compile
  • Vue.set
  • Vue.delete

Nuxt

Antes de finalizar, me gustaría hacer mención especial a Nuxt ya que actualmente está usando Vue 2.x. La comunidad está pidiendo al equipo de Nuxt que actualicen a Vue 3 (podemos verlo en esta Issue de GitHub) pero parece que va a tardar algunos meses más, puesto que aún están planteando qué cambios va a traer además de la actualización de Vue. Podemos encontrar estos planes en una presentación oficial.

Todo lo que hemos visto es un breve repaso sobre algunas funcionalidades que hemos comentado. El desarrollo de esta versión ha llevado unos 2 años por un gran equipo, es imposible contarlo en un solo artículo por lo que te invito encarecidamente a que eches una ojeada a los enlaces que te pongo al final del artículo.

¿Merece la pena pasar a esta versión? 👍 👎

Personalmente pienso que depende. Si tu aplicación es muy grande o simplemente crees que va a serlo y quieres que escale sin problemas creo que es un acierto pasar a esta nueva versión. Introduce muchas mejoras para la performance, refactorización y reutilización de código que son útiles para aplicaciones de gran tamaño.

Si simplemente aún no has empezado con tu aplicación o te vas a introducir en Vue, te aseguro que es una VUEnisima eleccion empezar con Vue 3.

Enlaces de Interés

luffy despidiendose
Cristian Poley

Me encanta el Frontend y esculpo páginas webs, pero ante todo: Ingeniero Informático. En contra de lo políticamente correcto. Ciudadano del mundo.