¡Bienvenidos a esta serie de posts sobre patrones de diseño en Vue! Para poder seguir esta serie, les recomendamos tener al menos una noción básica sobre Vue, ya que acá no vamos a discutir conceptos básicos sino las distintas maneras de hacer las cosas en Vue y los pros y contras de cada una. En esta primera edición, vamos a ver la declaración de nuestros componentes. ¿Qué maneras hay de hacerlo, y cuáles nos convienen para cada caso? Averigüémoslo…
En primer lugar, tenemos que distinguir entre dos formas generales de declarar componentes en Vue: en archivos con extensión .js mediante el uso del constructor component() del objeto Vue o en archivos dedicados con extensión .vue (llamados Single File Components o SFC). Empecemos con los primeros.
Declaración mediante Vue.component()
Dentro de un archivo con extensión .js, declaramos un componente mediante la estructura:
Vue.component(‘nombre-en-kebab-case’, {
template: ‘template’,
data(): {},
methods: {}
});
Antes de adentrarnos en las diferentes maneras de encarar este tipo de declaración con ejemplos, ¿cuáles son los pros y contras de este patrón?
Pros
- No necesitamos un builder como Webpack para generar un archivo final que los navegadores puedan entender. Este archivo que escribimos es lo que se termina desplegando en el producto final.
Contras
- Los componentes se registran a nivel global, por lo que:
- todos los componentes deben tener un nombre único.
- todos los componentes están disponibles en todas partes de nuestro código Javascript. Esto puede llevar, por ejemplo, a que si tenemos un componente ‘B’ importado dentro de un componente ‘A’, el componente ‘A’ esté a su vez disponible para ser importado dentro del componente ‘B’. Si por error generamos ese círculo de dependencias, nos podemos encontrar con errores bastante extraños.
- No aprovechamos todas las ventajas de los IDEs y editores que trabajan bien con Vue, como WebStorm y Visual Studio Code.
Debido a estos contras, esta manera de trabajar con Vue no suele recomendarse (por ejemplo, se la menciona solo a modo introductorio en la documentación oficial) y directamente se plantea como inmanejable para proyectos de mayor envergadura.
Para analizar las diferentes formas que adopta este patrón, vamos a ver el mismo ejemplo de un botón con un número que va aumentando a medida que vamos haciendo click en el botón:
1) String template
Vue.component('string-template', {
template: '<button class="btn-primary" @click.prevent="handleClick"><slot></slot>(Cantidad de clicks: {{contador}})</button>',
data() {
return {
contador: 0
};
},
methods: {
handleClick() {
this.contador++;
console.log('Click ', this.contador);
},
},
});
En este caso, el template HTML del componente es un string delimitado por comillas simples (‘), sin quiebres de línea en el medio.
Pros
- Compatibilidad directa con ES5 (ej: Internet Explorer 11 o anteriores), sin necesidad de transpilar el código usando Babel o una solución similar.
Contras
- Todo el template en una línea sin ningún tipo de indentación o quiebre de línea.
- Ningún tipo de resaltado de sintaxis, sugerencias ni predicciones para el template en los IDE.
2) Template literal
Vue.component('template-literal', {
template: `
<button class="btn-primary" @click.prevent="handleClick">
<slot></slot>(Cantidad de clicks: {{contador}})
</button>
`,
data() {
return {
contador: 0
};
},
methods: {
handleClick() {
this.contador++;
console.log('Click ', this.contador);
},
},
});
En este caso, el template HTML del componente es un string delimitado por backticks (`), con quiebres de línea e indentación.
Pros
- Posibilidad de usar indentación y quiebres de línea
Contras
- Las mismas que en el caso anterior, sumado a que no es compatible con ES5, por lo que necesitamos transpilarlo con Babel o una solución similar si queremos que sea compatible con navegadores más avejentados.
3) Funcion render()
Vue.component('funcion-render', {
data() {
return {
contador: 0
};
},
methods: {
handleClick() {
this.contador++;
console.log('Click ', this.contador);
},
},
render(crearElemento) {
return crearElemento(
'button',
{
attrs: {
class: 'btn-primary',
},
on: {
click: this.handleClick,
},
},
this.$slots.default
);
}
});
En este caso, el componente ya no tiene un atributo “template”, sino una función “render()”. Esta función recibe un objeto que aquí nombramos “crearElemento”, que resulta ser una función provista por Vue para crear elementos HTML.
Pros
- Nos permite crear el template programáticamente, ideal para escribir componentes repetidos con mínimas variaciones.
Contras
- Menos legibilidad.
4) Template JSX
Vue.component('jsx-template', {
data() {
return {
contador: 0
};
},
methods: {
handleClick() {
this.contador++;
console.log('Click ', this.contador);
},
},
render(h) {
return (
<button class="btn-primary" @click.prevent="handleClick">
{this.$slots.default}(clicked - {{contador}})
</button>
);
}
});
En este caso, si bien tenemos una función render, vamos a construir el template usando el lenguaje JSX. Este lenguaje de templating, propio de React, está disponible en Vue gracias a un plugin de Babel. Uno de los requisitos para poder usar este método es que el alias de nuestro objeto de creación de elementos sea “h”.
Pros
- Podemos usar JSX en Vue, práctico si ya sabemos React y en general más claro que una función render tradicional.
Contras
- Debemos recordar recibir la función de creación de elementos con el nombre “h”.
- Debemos usar Babel, y el plugin de Babel correspondiente, en nuestro proyecto.
- JSX en Vue no es algo soportado por muchos IDEs o editores. De hecho, la extensión más popular de Vue en VS Code, Vetur, no lo parsea correctamente.
Declaración mediante Single File Component (SFC)
1) Single File Component estándar
<template>
<p class="demo">
<button class="btn-primary" @click.prevent="handleClick">
<slot></slot>(clicked - {{contador}})
</button>
</p>
</template>
<script>
export default {
data() {
return {
contador: 0,
};
},
methods: {
handleClick() {
this.contador++;
console.log('clicked', this.contador);
},
},
};
</script>
<style scoped>
.btn-primary {
display: inline-block;
font-size: 1.2rem;
color: #fff;
background-color: #3eaf7c;
padding: 0.8rem 1.6rem;
border-radius: 4px;
transition: background-color 0.1s ease;
box-sizing: border-box;
border-bottom: 1px solid #389d70;
}
</style>
Pros
- No se registran a nivel global, sino que se deben importar al HTML cuando se los necesita. Por ende, evitamos todos los problemas que genera el registro global de componentes.
- Contienen toda la información del componente: HTML, CSS y JS.
- Es “la” manera de trabajar con Vue, lo cual nos garantiza una máxima compatibilidad con herramientas diseñadas para Vue como el plugin Vetur de VS Code, formatters y linters.
Contras
- Son archivos que deben compilarse (generalmente mediante Webpack) para generar archivos finales HTML, CSS y JS que el navegador pueda entender.
2) Vue Class Component
<template>
<button class="btn-primary" @click.prevent="handleClick">
<slot></slot>(clicked - {{ contador }})
</button>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
@Component({
//Props
props: {
colorBoton: String
}
})
export default EjemploVCC extends Vue {
//Data
contador = 0;
//Methods
handleClick() {
this.contador++;
console.log('clicked', this.contador);
}
//Computed property
get random() {
return Math.random();
}
//Hook
mounted () {
this.contador = 0
}
}
</script>
<style scoped>
.btn-primary {
background-color: blue;
}
</style>
Vue Class Component es un patrón de declaración de componentes en Vue que requiere el uso de Babel o TypeScript en nuestro proyecto y sirve para escribir los SFC en un formato más parecido al de una clase, por ejemplo de Java.
En el caso del ejemplo anterior, podemos ver comentarios que nos indican cómo se escriben las diferentes partes del componente. Podemos apreciar el formato similar a clase que queda cuando los “data” se declaran como atributos, los datos calculados como getters y los métodos como funciones sueltas dentro del componente, sin envolverlas en un objeto “methods”.
Pros
- Todas las ventajas de los SFC vistas en el punto anterior, con la excepción de que nos es tan compatible con los IDE y editores.
- Nos permite organizar la estructura de los componentes con una sintaxis más parecida a una clase.
Contras
- Requiere Babel o TypeScript, y un plugin específico de cada uno para funcionar.
- Cambia el comportamiento normal del "this" de JavaScript en algunas situaciones.
- Los data que tienen "undefined" como valor inicial no van a ser reactivos (por ende, conviene inicializar todos los data).
Llegamos al final de este artículo. Esperamos que este pantallazo general de los diferentes patrones de diseño de declaración de componentes brinde una idea de cuáles conviene usar en cada situación y proyecto.
Los esperamos en el próximo artículo de esta serie, que va a estar dedicado a los diferentes patrones para la comunicación entre los componentes. ¡Hasta pronto!