Conceptos Básicos de Componentes

Ejemplo base

Aquí un ejemplo de un componente Vue:

// Definir un nuevo componente llamado button-counter
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">Me ha pulsado {{ count }} veces.</button>'
})

Los componentes son instancias reutilizables de Vue con un nombre: en este caso, <button-counter>. Podemos usar este componente como un elemento personalizado dentro de una instancia de Vue raíz creada con new Vue:

<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

Dado que los componentes son instancias reutilizables de Vue, aceptan las mismas opciones que new Vue, como data, computed, watch, methods, y hooks de ciclo de vida. Las únicas excepciones son algunas opciones específicas de la raíz como el.

Reutilizando Componentes

Los componentes se pueden reutilizar tantas veces como se desee:

<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>

Tenga en cuenta que al hacer clic en los botones, cada uno mantiene su propio count por separado. Esto se debe a que cada vez que utiliza un componente, se crea una nueva instancia del mismo.

data Debe ser una función

Cuando definimos el componente <button-counter>, es posible que haya notado que data no devuelve directamente un objeto, como este:

data: {
count: 0
}

En lugar de eso, la opción data de un componente debe ser una función, de modo que cada instancia pueda mantener una copia independiente del objeto de datos devuelto:

data: function () {
return {
count: 0
}
}

Si Vue no tuviera esta regla, hacer clic en un botón afectaría los datos de todas las demás instancias, como a continuación:

Organización de Componentes

Es común que una aplicación se organice en un árbol de componentes anidados:

Component Tree

Por ejemplo, puede tener componentes para un encabezado, una barra lateral y un área de contenido, cada uno de los cuales generalmente contiene otros componentes para enlaces de navegación, publicaciones de blog, etc.

Para usar estos componentes en templates, deben registrarse para que Vue los conozca. Existen dos tipos de registro de componentes: global y local. Hasta ahora, solo hemos registrado componentes globalmente, usando Vue.component:

Vue.component('my-component-name', {
// ... opciones ...
})

Los componentes registrados globalmente se pueden usar en el template de cualquier instancia de Vue raíz (new Vue) creada posteriormente, e incluso dentro de todos los subcomponentes del árbol de componentes de esa instancia de Vue.

Eso es todo lo que necesita saber sobre el registro por ahora, pero una vez que haya terminado de leer esta página y se sienta cómodo con su contenido, le recomendamos volver más tarde para leer la guía completa de Registro de Componentes.

Pasando datos a componentes secundarios con Props

Anteriormente, mencionamos la creación de un componente para publicaciones de blog. El problema es que ese componente no será útil a menos que puedas pasarle datos, como el título y el contenido de la publicación específica que queremos mostrar. Ahí es donde entran las props.

Las props son atributos personalizados que usted puede registrar en un componente. Cuando se pasa un valor a un atributo prop, se convierte en una propiedad en esa instancia de componente. Para pasar un título a nuestro componente de publicación de blog, podemos incluirlo en la lista de props que este componente acepta, usando la opción props:

Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})

Un componente puede tener tantas props como se desee, y se puede pasar cualquier valor a cualquier prop de forma predeterminada. En el template anterior, verá que podemos acceder a este valor en la instancia del componente, al igual que con data.

Una vez que se registra un prop, puede pasarle datos como un atributo personalizado, de la siguiente manera:

<blog-post title="Mi viaje con Vue"></blog-post>
<blog-post title="Blogging con Vue"></blog-post>
<blog-post title="Por qué Vue es tan divertido?"></blog-post>

En una aplicación típica, sin embargo, es probable que tenga un array de post en data:

new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'Mi viaje con Vue' },
{ id: 2, title: 'Blogging con Vue' },
{ id: 3, title: 'Por qué Vue es tan divertido?' }
]
}
})

Entonces querrá renderizar un componente para cada uno:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>

Arriba, verá que podemos usar v-bind para pasar propiedades dinámicamente. Esto es especialmente útil cuando no se conoce el contenido exacto que se va a renderizar con anticipación, como cuando se obtienen posts de una API.

Esto es todo lo que necesita saber sobre propiedades por ahora, pero una vez que haya terminado de leer esta página y se sienta cómodo con su contenido, le recomendamos volver más tarde para leer la guía completa de Propiedades.

Un elemento de una sola raíz

Al crear un componente <blog-post>, su plantilla eventualmente no contendrá más que solo el título:

<h3>{{ title }}</h3>

Como mínimo, querrá incluir el contenido del post:

<h3>{{ title }}</h3>
<div v-html="content"></div>

Sin embargo, si intenta esto en su plantilla, Vue mostrará un error, explicando que cada componente debe tener un solo elemento raíz. Puede corregir este error envolviendo la plantilla en un elemento principal de la siguiente manera:

<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>

A medida que nuestro componente crezca, es probable que no solo necesitemos el título y el contenido de una publicación, sino también la fecha de publicación, los comentarios y más. Definir una propiedad para cada pieza de información relacionada podría volverse muy molesto:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>

Por lo tanto, este podría ser un buen momento para refactorizar el componente <blog-post> para que acepte una única propiedad post:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})

El ejemplo anterior y algunos que veremos más adelante, utilizan Plantillas de cadena de texto de JavaScript para hacer que las plantillas multilínea sean más legibles. Internet Explorer (IE) no las admite, por lo tanto, si debe ser compatible con IE y no está transpilando (por ejemplo, con Babel o TypeScript), usa escapes de nueva línea en su lugar

.

Ahora, cada vez que se agreguen nuevas propiedadaes al objeto post, estarán automáticamente disponible dentro de <blog-post>.

Enviando mensajes a componentes padre con eventos

A medida que desarrollamos nuestro componente <blog-post>, es posible que algunas funciones requieran la comunicación hacia el componente padre. Por ejemplo, podemos decidir incluir una función de accesibilidad para ampliar el texto de las publicaciones del blog, dejando el resto de la página en su tamaño por defecto:

En el padre, podemos admitir esta función agregando una propiedad postFontSize en data:

new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})

Esta propiedad puede ser usada en la plantilla para controlar el tamaño de la fuente de todas las publicaciones del blog:

<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>

Ahora agreguemos un botón para ampliar el texto justo antes del contenido de cada publicación:

Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>
Agrandar texto
</button>
<div v-html="post.content"></div>
</div>
`
})

El problema es que este botón no hace nada:

<button>
Agrandar texto
</button>

Cuando hacemos clic en el botón, debemos comunicar al componente padre que debe agrandar el texto de todas las publicaciones. Afortunadamente, las instancias de Vue proporcionan un sistema de eventos personalizados para resolver este problema. Para emitir un evento a los padres, podemos llamar al método $emit, pasando el nombre del evento:

<button v-on:click="$emit('enlarge-text')">
Agrandar texto
</button>

Luego, en nuestro blog post, podemos escuchar este evento con v-on, tal como lo haríamos con un evento DOM nativo:

<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

Emitiendo un valor con un Evento

A veces es útil emitir un valor específico con un evento. Por ejemplo, podemos querer que el componente <blog-post> se encargue de cuánto agrandar el texto. En esos casos, podemos usar el segundo parámetro de $emit para proporcionar este valor:

<button v-on:click="$emit('enlarge-text', 0.1)">
Agrandar texto
</button>

Luego, cuando escuchamos el evento en el componente padre, podemos acceder al valor del evento emitido con $event:

<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>

O, si el controlador de eventos es un método:

<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>

Entonces el valor se pasará como el primer parámetro de ese método:

methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

Usando v-model en Componentes

Los eventos personalizados también se pueden usar para crear inputs personalizados que funcionan con v-model. Recuerde que:

<input v-model="searchText">

hace lo mismo que:

<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>

Cuando se usa en un componente, v-model en su lugar hace esto:

<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

Para que esto realmente funcione, el <input> dentro del componente debe:

Aquí está en acción:

Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})

Ahora v-model debería funcionar perfectamente con este componente:

<custom-input v-model="searchText"></custom-input>

Por ahora, eso es todo lo que necesita saber sobre los eventos de componentes personalizados, pero una vez que haya terminado de leer esta página y se sienta cómodo con su contenido, le recomendamos volver más tarde para leer la guía completa sobre Eventos Personalizados.

Distribución de contenido con Slots

Al igual que con los elementos HTML, a menudo es útil poder pasar contenido a un componente, como este:

<alert-box>
Algo ha ocurrido mal.
</alert-box>

Lo que podría renderizar algo como:

Algo ha ocurrido mal.

Afortunadamente, esta tarea se hace muy simple con el elemento personalizado <slot> de Vue:

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

Como verá más arriba, solo agregamos la ranura a la que queremos que el contenido vaya – y eso es todo. Hemos terminado!

Eso es todo lo que necesita saber acerca de slots por ahora, pero una vez que haya terminado de leer esta página y se sienta cómodo con su contenido, le recomendamos que regrese más tarde para leer la guía completa de Slots.

Componentes dinámicos

A veces, es útil cambiar dinámicamente entre componentes, como en una interfaz con pestañas:

Lo anterior es posible gracias al elemento <component> de Vue con el atributo especial is:

<!-- El componente cambia cuando currentTabComponent cambia -->
<component v-bind:is="currentTabComponent"></component>

En el ejemplo anterior, currentTabComponent puede contener:

Vea este fiddle para experimentar con el código completo, o esta versión para un ejemplo de enlace o binding al objeto de opciones de un componente, en lugar de su nombre registrado.

Eso es todo lo que necesita saber sobre los componentes dinámicos por ahora, pero una vez que haya terminado de leer esta página y se sienta cómodo con su contenido, le recomendamos volver más tarde para leer la guía completa sobre Componentes Dinámicos y Asíncronos.

Casos especiales de análisis de plantillas DOM.

Algunos elementos HTML, como <ul>, <ol>, <table> y <select> tienen restricciones sobre qué elementos pueden aparecer dentro de ellos, y algunos elementos como <li>, <tr> y <option> solo pueden aparecer dentro de ciertos otros elementos.

Esto conducirá a problemas cuando se utilizan componentes con elementos que tienen tales restricciones. Por ejemplo:

<table>
<blog-post-row></blog-post-row>
</table>

El componente personalizado <blog-post-row> se colocará como contenido no válido, lo que provocará errores en el resultado final. Afortunadamente, el atributo especial is ofrece una solución alternativa:

<table>
<tr is="blog-post-row"></tr>
</table>

Debe tenerse en cuenta que esta limitación no se aplica si está utilizando plantillas de cadenas de texto de una de las siguientes fuentes:

Eso es todo lo que necesita saber sobre los casos especiales de análisis de plantillas DOM por ahora, y en realidad, el final de los aspectos esenciales de Vue. ¡Felicidades! Todavía hay más que aprender, pero primero, recomendamos tomar un descanso para practicar con Vue usted mismo y construir algo divertido.

Una vez que se sienta cómodo con el conocimiento que acaba de digerir, le recomendamos que regrese para leer la guía completa de Componentes Dinámicos y Asíncronos, así como las otras páginas en la sección Componentes en Profundidad de la barra lateral.