Introducción: ¿Qué Es Vue - JS?
Introducción: ¿Qué Es Vue - JS?
¿Qué es Vue.js?
Vue (pronunciado /vjuː/ en inglés, como view) es un framework progresivo para
construir interfaces de usuario. A diferencia de otros frameworks monolíticos, Vue está
diseñado desde el inicio para ser adoptado incrementalmente. La biblioteca principal
se enfoca solo en la capa de la vista, y es muy simple de utilizar e integrar con otros
proyectos o bibliotecas existentes. Por otro lado, Vue también es perfectamente capaz
de soportar aplicaciones sofisticadas de una sola página (en inglés single-page-
application o SPA) cuando se utiliza en combinación con herramientas
modernas y librerías compatibles.
Empezando
Condicionales y bucles
Este ejemplo demuestra que no solo podemos enlazar datos con texto y atributos,
sino también con la estructura del DOM. Además, Vue provee un sistema de
transiciones muy poderoso que puede aplicar automáticamente efectos de
transición cuando los elementos son agregados/actualizados/removidos por Vue.
Hay unas cuantas otras directivas, cada una con una funcionalidad especial. Por
ejemplo, la directiva v-for puede ser utilizada para mostrar una lista de elementos
usando los datos de un array:
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
})
1. Learn JavaScript
2. Learn Vue
3. Build something awesome
En la consola, escribe app4.todos.push({ text: 'New item' }). Deberías ver un nuevo elemento
agregado a la lista.
Para permitir a los usuarios interactuar con tu aplicación, podemos usar la directiva v-
onpara añadir listeners de eventos que invocan métodos en nuestras instancias de
Vue:
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
Hello Vue.js!
Reverse Message
Pero esto renderizaría el mismo texto para cada todo, lo cual no es muy interesante.
Deberíamos ser capaces de pasar datos desde el padre a los componentes hijo.
Vamos a modificar la definición del componente para aceptar propiedades:
Vue.component('todo-item', {
// El componente todo-item ahora acepta
// "prop", el cual es similar a un atributo personalizado
// La rpopiedad se llama _todo_.
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
Ahora podemos pasar todo a cada componente repetido utilizando v-bind:
<div id="app-7">
<ol>
<!-- Ahora le pasamos a cada todo-item with el objeto todo -->
<!-- que representa, para que su contenido pueda ser dinámico -->
<todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
</ol>
</div>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ text: 'Vegetables' },
{ text: 'Cheese' },
{ text: 'Whatever else humans are supposed to eat' }
]
}
})
1. Vegetables
2. Cheese
3. Whatever else humans are supposed to eat
Puedes haber notado que los componentes de Vue son muy similares a
los Elementos Personalizados (Custom Elements), los cuales son parte de
la especificación de Componentes Web (Web Components). Esto es porque la
sintaxis de componente de Vue está modelada basándose en ideas de la
especificación. Por ejemplo, los componentes de Vue implementan la API de Slot y el
atributo especial is. Sin embargo, hay unas cuantas diferencias clave:
Constructor
Cuando crees una nueva instancia de Vue, necesitas pasar un objeto de opciones el
cual puede contener opciones para datos, una plantilla, el elemento donde montarla,
métodos, callbacks para el ciclo de vida, etc. Puedes encontrar la lista completa de
opciones en la documentación de referencia de la API.
El constructor de Vue puede ser extendido para crear constructores de
componentesreutilizables con opciones predefinidas:
var MyComponent = Vue.extend({
// opciones de extensión
})
// todas las instancias de `MyComponent` son creadas con
// las opciones de extensión predefinidas
var myComponentInstance = new MyComponent()
Propiedades y métodos
Consult the API reference for the full list of instance properties and methods.
Each Vue instance goes through a series of initialization steps when it is created - for
example, it needs to set up data observation, compile the template, mount the instance
to the DOM, and update the DOM when data changes. Along the way, it will also
invoke some lifecycle hooks, which give us the opportunity to execute custom logic.
For example, the created hook is called after the instance is created:
var vm = new Vue({
data: {
a: 1
},
created: function () {
// `this` points to the vm instance
console.log('a is: ' + this.a)
}
})
// -> "a is: 1"
There are also other hooks which will be called at different stages of the instance’s
lifecycle, for example mounted, updated, and destroyed. All lifecycle hooks are called with
their this context pointing to the Vue instance invoking it. You may have been
wondering where the concept of “controllers” lives in the Vue world and the answer is:
there are no controllers. Your custom logic for a component would be split among
these lifecycle hooks.
Lifecycle Diagram
Below is a diagram for the instance lifecycle. You don’t need to fully understand
everything going on right now, but this diagram will be helpful in the future.
Sintaxis de plantillas
Vue.js utiliza una sintaxis de plantilla basada en HTML lo que te permite enlazar
declarativamente el DOM con los datos de la instancia de Vue subyacente. Todas las
planitllas de Vue.js están compuestas por HTML válido que puede ser analizadas por
navegadores compatibles con las especifiaciones o analizadores HTML.
Si estas familiarizado con los conceptos del DOM Virtual y prefieres el poder de
JavaScript puro, puedes también escribir directamente funciones de
renderizado en lugar de plantillas, con soporte opcional para JSX.
Interpolations
Text
The most basic form of data binding is text interpolation using the “Mustache” syntax
(double curly braces):
<span>Message: {{ msg }}</span>
The mustache tag will be replaced with the value of the msg property on the
corresponding data object. It will also be updated whenever the data
object’s msg property changes.
You can also perform one-time interpolations that do not update on data change by
using the v-once directive, but keep in mind this will also affect any binding on the
same node:
<span v-once>This will never change: {{ msg }}</span>
HTML Puro
Las llaves dobles interpretan los datos como texto plano, no HTML. Si deseas mostrar
HTML real, necesitarás usar la directiva v-html:
<div v-html="rawHtml"></div>
El contenido es insertado como HTML puro - los enlaces de datos son ignorados.
Nota que no puedes utilizar v-html para componer plantillas parciales, porque Vue no
es un motor de plantillas basado en cadenas de texto. En su lugar, se prefiere utilizar
a los componentes como unidad fundamental para la reutilización de UI y la
composición.
Renderizar dinámicamente HTML arbitrario en tu sitio web puede ser muy peligroso ya
que conduce a vulnerabilidades XSS. Utiliza interpolación HTML solo con contenido
de confianza y nunca con contenido provisto por el usuario.
Atributos
Las llaves no deben ser utilizadas dentro de atributos HTML, en su lugar utiliza
la directiva v-bind:
<div v-bind:id="dynamicId"></div>
También funciona para atributos booleanos - el atributo sera quitado si la condición se
evalúa como falsa:
<button v-bind:disabled="someDynamicCondition">Button</button>
Directivas
Las directivas son atributos especiales identificadas con el prefijo v-. Los valores de
los atributos de directivas deben ser una sola expresión JavaScript (con la
excepción de v-for, el cual discutiremos luego). El trabajo de una directiva es aplicar
reactivamente efectos secundarios al DOM cuando el valor de su expresión cambia.
Veamos el ejemplo que utilizamos en la introducción:
<p v-if="seen">Now you see me</p>
Aquí, la directiva v-if removería/insertaría el elemento <p> basada en la veracidad del
valor de la expresión seen.
Argumentos
Algunas directivas pueden recibir un “argumento”, indentificado con dos puntos luego
del nombre de la directiva. Por ejemplo, la directiva v-bind se utiliza para actualizar
reactivamente un atributo HTML:
<a v-bind:href="url"></a>
Aquí href es el argumento, el cual le indica a la directiva v-bind que enlace el
atributo href del elemento con el valor de la expresiónurl.
Otro ejemplo es la directiva v-on, la cual escucha eventos del DOM:
<a v-on:click="doSomething">
Aquí el argumento es el nombre del evento que debe escuchar. Hablaremos acerca
del manejo de enventos en detalle tambíen.
Modificadores
Los modificadores son sufijos especiales identificados con un punto, los cuales
indican que la directiva debe ser enlazada de alguna forma especial. Por ejemplo, el
modificador .preventindica a la directiva v-on que ejecute event.preventDefault() en el evento
disparado:
<form v-on:submit.prevent="onSubmit"></form>
Veremos más usos de los modificadores cuando hablemos en detalle de v-on y v-model.
Filtros
Vue.js te permite definir filtros que pueden ser usados para aplicar formatos de texto
comunes. Pueden ser utilizados en dos lugares: en la interpolación con llaves y las
expresiones v-bind. Los filtros deben ser agregados al final de las expresiones
JavaScript, luego de un símbolo de tubería:
<!-- en interpolación de texto -->
{{ message | capitalize }}
<!-- en v-bind -->
<div v-bind:id="rawId | formatId"></div>
Los filtros de Vue 2.x solo pueden ser usados dentro de interpolaciónes con llaves y
expresiones v-bind (esto último soportado desde la versión 2.1.0) porque están
diseñados principalmente para transformar texto. Para transformaciones de datos más
complejas en otras directivas, deberías utilizar en su lugar propiedades computadas.
Atajo para v-bind
<!-- sintaxis completa -->
<a v-bind:href="url"></a>
<!-- atajo -->
<a :href="url"></a>
Atajo para v-on
<!-- sintaxis completa -->
<a v-on:click="doSomething"></a>
<!-- atajo -->
<a @click="doSomething"></a>
Pueden parecer un poco diferentes al HTML normal, pero : y @ son carácteres válidos
para nombres de atributo y todos los navegadores soportados por Vue.js los pueden
analizar correctamente. Además, no aparecen en la estructura renderizada final. La
sintaxis corta es totalmente opcional, pero seguramente te gustará cuando aprendas
más acerca de su uso.
Propiedades computadas y
observadores
Propiedades computadas
Las expresiones dentro de las plantillas son muy cómodas, pero están pensadas solo
para operaciones simples. Agregar demasiada lógica en tus plantillas puede hacerlas
engorrosas y difíciles de mantener. Por ejemplo:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
En este punto, la plantilla ya no es más sencilla y declarativa. Tienes que observarla
durante un momento antes de entender que muestra el valor de mesagge invertido. El
problema es peor cuando quieres incluir el mensaje invertido en más de un lugar
dentro de tu plantilla.
Por esto es que para cualquier lógica compleja, deberías utilizar una propiedad
computada.
Ejemplo básico
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// un getter computado
reversedMessage: function () {
// `this` apunta a la instancia de vm
return this.message.split('').reverse().join('')
}
}
})
Resultado:
Original message: "Hello"
Puede que hayas notado que podemos lograr el mismo resultado invocando un
método en la expresión:
<p>Reversed message: "{{ reverseMessage() }}"</p>
// dentro del componente
methods: {
reverseMessage: function () {
return this.message.split('').reverse().join('')
}
}
En lugar de una propiedad computada, podemos definir la misma función como un
método. Desde el punto de vista del resultado, ambos enfoques son exactamente
iguales. Sin embargo, la diferencia es que las propiedades computadas son
cacheadas basándose en sus dependencias. Una propiedad computada solo se
reevaluará cuando alguna de sus dependencias haya cambiado. Esto significa que
mientras que message no haya cambiado, acceder reiteradamente a la propiedad
computada reversedMessage devolverá inmediatamente el resultado computado
previamente sin tener que ejecutar la función de nuevo.
Esto también significa que la siguiente propiedad computada nunca se actualizará,
porque Date.now() no es una dependencia reactiva:
computed: {
now: function () {
return Date.now()
}
}
Vue provee una forma más generica de observar cambios de datos en una instancia
de Vue y reaccionar frente a ellos: observar propiedades. Cuando tienes algunos
datos que deben cambiar basándose en otros datos, es tentador utilizar
excesivamente watch - especialmente si tienes experiencia con AngularJS. De
cualquier manera, normalmente es una mejor idea utilizar una propiedad computada
en lugar de una llamada imperativa al la función callbackwatch. Por ejemplo:
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
Las propiedades computadas solo tienen una función getter por defecto, pero puedes
agregarles una función setter cuando lo necesites:
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
Ahora cuando ejecutes vm.fullName = 'John Doe', la función setter será ejecutada
y vm.firstName y vm.lastName serán actualizadas según corresponda.
Observadores
Mientras que las propiedades computadas son más apropiadas en la mayoría de los
casos, hay veces que un observador personalizado es necesario. Es por eso que Vue
provee una forma más genérica de reaccionar a los cambios en los datos a través de
la opción watch. Esto es mayormente útil cuando quieres realizar operaciones
asíncronas o costosas en respuesta a los cambios de los datos.
Por ejemplo:
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- Dado que ya existe un ecosistema rico de bibliotecas AJAX -->
<!-- y colecciones de métodos utilitarios de propósito general, el núcleo de Vue -->
<!-- es capaz de mantenerse pequeño porque no crea los suyos propios. Esto también -->
<!-- te da la libertad de utilizar cualquier cosa con la que ya estes familiarizado -->
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
<script src="https://unpkg.com/[email protected]/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// cuando 'question' cambie, se ejecutará esta función
question: function (newQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// _.debounce es una función probista por lodash para limitar cuan
// seguido una operación particularmente costosa puede ejecutarse.
// En este caso, queremos limitar las peticiones a
// yesno.wtf/api, esperando a que el usuario haya finalizado
// de tipear antes de hacer la petición ajax. Para aprender
// más acerca de la función _.debounce ( y su prima
// _.throttle), visita: https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// Este es el número de milisegundos que esperamos
// a que el usuario termine de tipear.
500
)
}
})
</script>
Resultado:
Sintaxis de objeto
Renderizará:
<div class="static active"></div>
Cuando isActive o hasError cambien, la lista de clases será actualizada en consecuencia.
Por ejemplo, si hasError pasa a valer true, la lista de clases se convertirá en "static active text-
danger".
Sintaxis de arreglo
Podemos pasar un arreglo a v-bind:class para aplicar una lista de clases:
<div v-bind:class="[activeClass, errorClass]">
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
Lo cual renderizará:
<div class="active text-danger"></div>
Si quisieras alternar una clase en la lista condicionalmente, puedes hacerlo con una
expresión ternaria:
<div v-bind:class="[isActive ? activeClass : '', errorClass]">
Esto siempre aplicará la clase errorClass, pero solo activeClass cuando isActivevalga true.
Sin embargo, esto puede resultar engorroso si tienes múltiples clases condicionales.
Es por eso que también es posible utilizar la sintaxis de objetos dentro de la sintaxis
de arreglos:
<div v-bind:class="[{ active: isActive }, errorClass]">
Dentro de componentes
Sintaxis de objeto
Normalmente es una buena idea enlazar a un objeto de estilo directamente para que
la plantilla sea más clara:
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
Sintaxis de arreglo
Prefijos automáticos
Renderizado condicional
v-if
v-else
Puedes utilizar la directiva v-else para indicar un “bloque else” para v-if:
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
Un elemento v-else debe encotrarse inmediatamente después de un elemento v-if o v-
else-if - de otra forma, no será reconocido.
v-else-if
Nuevo en 2.1.0
v-else-if, como su nombre lo indica, sirve como un “bloque else if” para v-if. Puede ser
encadenado varias veces seguidas:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
Similar a v-else, un elemento v-else-if debe ubicarse inmediatamente luego de un
elemento v-if o v-else-if.
Ahora esos campos de texto serán renderizados desde cero cada vez que los
intercambies. Verifícalo:
Username
Toggle login type
Nota que los elementos <label> están siendo reutilizados eficientemente, porque no
tienen el atributo key.
v-show
Otra opción para mostrar condicionalmente un elemento es la directiva v-show. El uso
es prácticamente el mismo:
<h1 v-show="ok">Hello!</h1>
La diferencia es que un elemento con v-show siempre será renderizado y permanecerá
en el DOM. v-show simplemente alterna el valor de la propiedad CSS display del
elemento.
Nota que v-show no soporta la sintaxis <template> ni funciona con v-else.
v-if vs v-show
v-if with v-for
Renderizado de listas
v-for
Podemos utilizar la directiva v-for para renderizar una lista de elementos basándonos
en un arreglo. La directiva v-for requiere una sintaxis especial de la forma item in items,
donde items es el arreglo de datos fuente e item es un alias para el elemento sobre el
que se está iterando:
uso básico
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Resultado:
Foo
Bar
Dentro de los bloques v-for tenemos acceso total a las propiedades del ámbito del
padre. v-for también soporta un segundo parámetro opcional para indicar el índice el
elemento actual.
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Resultado:
Parent - 0 - Foo
Parent - 1 - Bar
Puedes utilizar of como delimitador en lugar de in, para que la sintaxis sea más
parecida a la utilizada en JavaScript para iteradores:
<div v-for="item of items"></div>
v-for en
Similar a v-if, puedes utilizar una etiqueta <template> con v-for para renderizar un bloque
de múltiples elementos. Por ejemplo:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>
v-for con objetos
Puedes utilizar v-for para iterar a través de las propiedades de un objeto.
<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#repeat-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
Resultado:
John
Doe
30
v-for con rangos
v-for puede recibir un entero. En este caso, repetirá la plantilla esa cantidad de veces.
<div>
<span v-for="n in 10">{{ n }}</span>
</div>
Resultado:
1 2 3 4 5 6 7 8 9 10
Componentes y v-for
Esta sección asume un conocimiento previo de los componentes de Vue.
Siéntete libre de saltearla y volver luego.
Do the dishes X
Take out the trash X
Mow the lawn X
v-for con v-if
Cuando existen en el mismo nodo, v-for tiene mayor prioridad que v-if. Esto significa
que v-if será ejecutado en cada iteración del bucle separadamente. Esto es muy útil
cuando quieres renderizar nodos solo para algunos elementos, como:
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
Vue envuelve los métodos de mutacion de los arreglos observados por lo que también
disparará actualizaciones de las vistas. Los métodos envueltos son:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Reemplazando un arreglo
Puedes pensar que esto hará que Vue tiré todo el DOM existente y re-renderice la
lista entera. Por suerte, no es el caso. Vue implementa algunas heurísticas
inteligentes para reutilizar al máximo los elementos del DOM, por lo que reemplazar
un arreglo existente por otro que contiene objetos solapados es una operación muy
eficiente.
Advertencias
Por ejemplo:
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
Como alternativa, puedes utilizar un método donde las propiedades computadas no
son factibles (por ejemplo, dentro de bucles v-for anidados):
<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
Uso básico
Texto
Message is:
Texto multilínea
Checkbox
false
Radio
One
Two
Picked:
Select
Select simple:
<select v-model="selected">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
Selected:
Selected: A
Enlace de valores
Checkbox
<input
type="checkbox"
v-model="toggle"
v-bind:true-value="a"
v-bind:false-value="b"
>
// Cuando se tilda:
vm.toggle === vm.a
// cuando se destila:
vm.toggle === vm.b
Radio
opciones de Select
<select v-model="selected">
<!-- objeto literal en línea -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// cuando se selecciona:
typeof vm.selected // -> 'object'
vm.selected.number // -> 123
Modificadores
.lazy
Por defecto, v-model sincroniza el campo con los datos luego de cada evento input (con
la excepción de las composiciones IME como se mencionó anteriormente). Puedes
agregar el modificador lazy para sincronizar luego de eventos change en su lugar:
<!-- sincronizado luego de "change" en lugar de "input" -->
<input v-model.lazy="msg" >
.number
Si deseas que un campo de usuario sea convertido automáticamente a un número,
puedes agregar el modificador number a tus campos controlados por v-model:
<input v-model.number="age" type="number">
Esto es útil, debido a que incluso con type="number", el valor de los elementos
HTML <ìnput> siempre devuelven una cadena de texto.
.trim
Si deseas que a los datos de usuario se le aplique trim automáticamente, puedes
agregar el modificador trim a tus campos controlados por v-model:
<input v-model.trim="msg">
v-model con componentes
Si todavía no estas familiarizado con los componentes de Vue, saltea esto por
ahora.
Componentes
Los componentes son una de las características más poderosas de Vue. Te permiten
extender elementos HTML básicos para encapsular código reutilizable. En un nivel
alto, los componentes son elementos personalizados a los que el compilador de Vue
les añade comportamiento. En algunos casos, pueden aparecer como elementos
HTML nativos extendidos con el atributo especial is.
Utilizando componentes
Registro
Hemos aprendido en las secciones anteriores que podemos crear una nueva instancia
de Vue con:
new Vue({
el: '#some-element',
// opciones
})
Para registrar un componente global, puedes utilizar Vue.component(tagName, options). Por
ejemplo:
Vue.component('my-component', {
// opciones
})
Una vez registrado, un componente puede ser utilizado en la plantilla de una instancia
como un elemento personalizado <my-component></my-component>. Asegúrate que el
componente este registrado antes de crear la instancia principal de Vue. Aquí hay un
ejemplo completo:
<div id="example">
<my-component></my-component>
</div>
// registro
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// crear la instancia principal
new Vue({
el: '#example'
})
Lo cual renderizará:
<div id="example">
<div>A custom component!</div>
</div>
A custom component!
Registro local
<script type="text/x-template">
Por lo tanto, prefiere utilizar plantillas de texto siempre que sea posible.
Componiendo componentes
Propiedades
Resultado:
hello!
Los atributos HTML no distinguen entre mayúsculas y minúsculas, por lo que cuando
utilices plantillas que no sean de texto, los nombres de propiedades en
formato camelCase necesitan ser escritas con su equivalente kebab-case:
Vue.component('child', {
// camelCase en JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case en HTML -->
<child my-message="hello!"></child>
De nuevo, si estás utilizando plantillas de texto, entonces está limitación no aplica.
Propiedades dinámicas
Resultado:
Message from parent
Literal vs dinámico
Un error común que los principiantes suelen cometer es tratar de pasar un número
utilizando la sintaxis literal:
<!-- esto pasa la cadena de texto "1" -->
<comp some-prop="1"></comp>
Sin embargo, dado que esta es una propiedad literal, su valor se pasa como la cadena
de texto "1" en lugar de un número. Si en realidad queremos pasar un número
JavaScript, necesitamos utilizar v-bind para que su valor sea evaluado como una
expresión JavaScript:
<!-- esto pasa el numero 1 -->
<comp v-bind:some-prop="1"></comp>
Flujo de datos en un solo sentido
Además, cada vez que el componente padre es actualizado, todas las propiedades en
el componente hijo serán refrescadas con el último valor. Esto significa
que no deberías modificar una propiedad en un componente hijo. Si lo haces, Vue te
advertirá en la consola.
Hay usualmente dos casos en los que puede ser tentador modificar una propiedad:
1. La propiedad es utilizada solo para dar un valor inicial, el componente hijo solo
quiere utilizarla como una propiedad de datos local luego;
1. Define una propiedad de datos local que utilice el valor inicial de la propiedad
como su valor inicial:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
Nota que los objetos y arreglos en JavaScript son pasados por referencia, por lo que
si la propiedad es un arreglo u objeto, modificarlos dentro del hijo afectará al estado
del padre.
Validación de propiedades
String
Number
Boolean
Function
Object
Array
Hemos aprendido que los padres pueden pasar datos hacia los hijos utilizando
propiedades. Pero, ¿cómo nos comunicamos con el padre cuando algo sucede? Aquí
es donde el sistema de eventos personalizados de Vue entra en juego.
Nota que el sistema de eventos de Vue está separado de la API EventTarget API de
los navegadores. Aunque funcionan similarmente, $on y $emit not son alias
para addEventListener y dispatchEvent.
Además, un componente padre puede escuchar los eventos emitidos por un
componente hijo utilizando v-on directamente en la plantilla donde el componente hijo
está siendo utilizado.
No puedes utilizar $on para escuchar eventos emitidos por hijos. Debes utilizar v-
on directamente en la plantilla, como en el ejemplo debajo.
0 0
$
La interface de eventos puede ser usada para crear campos de entrada poco
comunes. Por ejemplo, imagina estas posibilidades:
<voice-recognizer v-model="question"></voice-recognizer>
<webcam-gesture-reader v-model="gesture"></webcam-gesture-reader>
<webcam-retinal-scanner v-model="retinalImage"></webcam-retinal-scanner>
Ámbito de compilación
Antes de sumergirnos en la API, clarifiquemos primero en que ámbito son compilados
los contenidos. Imagina una plantilla como esta:
<child-component>
{{ message }}
</child-component>
¿message debería estar enlazado a los datos del padre o del hijo? La respuesta es al
padre. Suna The answer is the parent. Una regla sencilla para recordar el ámbito de
los componentes es:
El contenido del padre será descartado a menos que la plantilla del componente hijo
contenga por lo menos un contenedor <slot>. Cuando haya solo un slot sin atributos, el
contenido completo será insertado en su posición en el DOM, reemplazandolo.
Cualquier contenido originalmente dentro de la etiqueta <slot> es considerado por
defecto. El contenido por defecto es compilado en el ámbito del hijo y solo será
mostrado si el elemento de alojamiento está vacío y no tiene contenido para ser
insertado.
Imagina que tenemos un componente llamado my-component con la siguiente plantilla:
<div>
<h2>I'm the child title</h2>
<slot>
This will only be displayed if there is no content
to be distributed.
</slot>
</div>
Nuevo en 2.1.0
Un caso de uso típico para slots con ámbito sería un componente lista que permita al
consumidor del componente personalizar como debería ser renderizado cada
elemento en la lista:
<my-awesome-list :items="items">
<!-- los 'slots' con ámbito pueden también tener nombre -->
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-awesome-list>
Componentes dinámicos
Puedes utilizar el mismo punto de montaje e intercambiar dinámicamente múltiples
componentes utilizando el elemento reservado <component> y enlazarlo dinámicamente a
su atributo is:
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
<component v-bind:is="currentView">
<!-- ¡el componente cambia cuando vm.currentView cambia! -->
</component>
keep-alive
Si quieres mantener en memoria los componentes que han sido sacados para
preservar su estado o evitar un re-renderizado, puedes envolver un componente
dinámico con un elemento <keep-alive>:
<keep-alive>
<component :is="currentView">
<!-- ¡los componentes inactivos serán guardados en una memoria caché! -->
</component>
</keep-alive>
Más detalles acerca de <keep-alive> en la referencia de la API.
Misc
Componentes asíncronos
Sin embargo, cuando utilices plantillas de texto, no estás limitado por las restricciones
de HTML. Eso significa que incluso en las plantillas, puedes referenciar a tus
componentes y propiedades utilizando camelCase, TitleCase, o kebab-case:
<!-- ¡utiliza lo que quieras en plantillas de texto! -->
<my-component></my-component>
<myComponent></myComponent>
<MyComponent></MyComponent>
Si tu componente no recibe contenido a través de elementos slot, puedes incluso hacer
que se auto-cierre con una / luego del nombre:
<my-component/>
Componentes recursivos
¡Problema resuelto!
Plantillas en línea
Cuando el atributo especial inline-template está presente en un componente hijo, el
componente utilizará su propio contenido interno como su plantilla, en lugar de tratarlo
como contenido distribuido. Esto permite una mayor flexibilidad en la creación de
plantillas.
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
Sin embargo, inline-template hace más difícil razonar acerca del ámbito de nuestra
plantilla. Como una buena práctica, es preferible definir plantillas dentro de
componentes utilizando la opción template o en un elemento template dentro de un
archivo .vue.
X-Templates
Esto puede ser útil para demostraciones con plantillas grandes o en aplicaciones
extremadamente pequeñas, pero debe ser evitado en otro caso, porque separan las
plantillas del resto de la definición del componente.