Fundamentos de Componentes
Los componentes nos permiten dividir la interfaz de usuario en piezas independientes y reutilizables, y pensar en cada pieza de forma aislada. Es común que una aplicación se organice en un árbol de componentes anidados:

Esto es muy similar a cómo anidamos los elementos HTML nativos, pero Vue implementa su propio modelo de componentes que nos permite encapsular contenido y lógica personalizada en cada componente. Vue también funciona bien con los Web Components nativos. Si tienes curiosidad sobre la relación entre los Componentes de Vue y los Web Components nativos, lee más aquí.
Definición de un Componente
Cuando se utiliza un paso de compilación, normalmente definimos cada componente de Vue en un archivo dedicado utilizando la extensión .vue - conocido como un Componente de Archivo Único (SFC, por sus siglas en inglés):
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Me presionaste {{ count }} veces.</button>
</template>Cuando no se utiliza un paso de compilación, un componente de Vue puede definirse como un objeto JavaScript plano que contiene opciones específicas de Vue:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
Me presionaste {{ count }} veces.
</button>`
// También puede apuntar a un template dentro del DOM:
// template: '#my-template-element'
}Aquí el template se incluye en línea como una cadena de JavaScript, que Vue compilará sobre la marcha. También puedes usar un selector de ID que apunte a un elemento (normalmente elementos <template> nativos); Vue utilizará su contenido como fuente de el template.
El ejemplo anterior define un solo componente y lo exporta como la exportación por defecto de un archivo .js, pero puedes usar exportaciones con nombre para exportar múltiples componentes desde el mismo archivo.
Uso de un Componente
TIP
Utilizaremos la sintaxis SFC para el resto de esta guía; los conceptos relacionados con los componentes son los mismos independientemente de si estás utilizando un paso de compilación o no. La sección de Ejemplos muestra el uso de componentes en ambos escenarios.
Para usar un componente hijo, necesitamos importarlo en el componente padre. Suponiendo que colocamos nuestro componente contador dentro de un archivo llamado ButtonCounter.vue, el componente se expondrá como la exportación por defecto del archivo:
vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>¡Aquí hay un componente hijo!</h1>
<ButtonCounter />
</template>Con <script setup>, los componentes importados se hacen disponibles automáticamente en el template.
También es posible registrar globalmente un componente, haciéndolo disponible para todos los componentes de una aplicación dada sin necesidad de importarlo. Los pros y los contras del registro global frente al local se discuten en la sección dedicada a Registro de Componentes.
Los componentes pueden reutilizarse tantas veces como quieras:
template
<h1>¡Aquí hay muchos componentes hijos!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />Observa que al hacer clic en los botones, cada uno mantiene su propio count separado. Esto se debe a que cada vez que utilizas un componente, se crea una nueva instancia de este.
En los SFC, se recomienda usar nombres de etiqueta PascalCase para los componentes hijos para diferenciarlos de los elementos HTML nativos. Aunque los nombres de etiqueta HTML nativos no distinguen entre mayúsculas y minúsculas, Vue SFC es un formato compilado, por lo que podemos usar nombres de etiqueta que sí distinguen entre mayúsculas y minúsculas. También podemos usar /> para cerrar una etiqueta.
Si estás creando tus templates directamente en el DOM (por ejemplo, como contenido de un elemento <template> nativo), el template estará sujeta al comportamiento de análisis HTML nativo del navegador. En tales casos, deberás usar kebab-case y etiquetas de cierre explícitas para los componentes:
template
<!-- si esta template es escrita en el DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>Consulta Consideraciones sobre el Análisis de Templates en el DOM para más detalles.
Pasando Props
Si estamos construyendo un blog, es probable que necesitemos un componente que represente una entrada de blog. Queremos que todas las entradas de blog compartan el mismo diseño visual, pero con contenido diferente. Un componente así no será útil a menos que puedas pasarle datos, como el title y el contenido de la entrada específica que queremos mostrar. Ahí es donde entran las props.
Las props son atributos personalizados que puedes registrar en un componente. Para pasar un title a nuestro componente de entrada de blog, debemos declararlo en la lista de props que este componente acepta, utilizando la macro defineProps:
vue
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>defineProps es una macro en tiempo de compilación que solo está disponible dentro de <script setup> y no necesita ser importada explícitamente. Las props declaradas se exponen automáticamente a el template. defineProps también devuelve un objeto que contiene todas las props pasadas al componente, de modo que podemos acceder a ellas en JavaScript si es necesario:
js
const props = defineProps(['title'])
console.log(props.title)Ver también: Tipado de Props de Componentes
Si no estás usando <script setup>, las props deben declararse utilizando la opción props, y el objeto props se pasará a setup() como primer argumento:
js
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}Un componente puede tener tantas props como desees y, por defecto, cualquier valor puede pasarse a cualquier prop.
Una vez que una prop está registrada, puedes pasarle datos como un atributo personalizado, así:
template
<BlogPost title="Mi experiencia con Vue" />
<BlogPost title="Bloguear con Vue" />
<BlogPost title="Por qué Vue es tan divertido" />En una aplicación típica, sin embargo, es probable que tengas un array de entradas en tu componente padre:
js
const posts = ref([
{ id: 1, title: 'Mi experiencia con Vue' },
{ id: 2, title: 'Bloguear con Vue' },
{ id: 3, title: 'Por qué Vue es tan divertido' }
])Luego querrás renderizar un componente para cada uno, usando v-for:
template
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>Observa cómo se utiliza la sintaxis de v-bind (:title="post.title") para pasar valores de prop dinámicos. Esto es especialmente útil cuando no conoces el contenido exacto que vas a renderizar de antemano.
Eso es todo lo que necesitas saber sobre las props por ahora, pero una vez que hayas terminado de leer esta página y te sientas cómodo con su contenido, te recomendamos volver más tarde para leer la guía completa sobre Props.
Escuchando Eventos
A medida que desarrollamos nuestro componente <BlogPost>, algunas características pueden requerir comunicarse de vuelta al padre. Por ejemplo, podríamos decidir incluir una característica de accesibilidad para agrandar el texto de las entradas de blog, mientras que el resto de la página permanece en su tamaño por defecto.
En el componente padre, podemos soportar esta característica añadiendo una ref postFontSize:
js
const posts = ref([
/* ... */
])
const postFontSize = ref(1)Que puede usarse en el template para controlar el tamaño de la fuente de todas las entradas de blog:
template
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>Ahora agreguemos un botón a el template del componente <BlogPost>:
vue
<!-- omitiendo <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Agrandar texto</button>
</div>
</template>El botón no hace nada todavía; queremos que al hacer clic en el botón se comunique al padre que debe agrandar el texto de todas las entradas. Para resolver este problema, los componentes proporcionan un sistema de eventos personalizado. El padre puede elegir escuchar cualquier evento en la instancia del componente hijo con v-on o @, tal como lo haríamos con un evento DOM nativo:
template
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>Entonces el componente hijo puede emitir un evento sobre sí mismo llamando al método integrado $emit, pasando el nombre del evento:
vue
<!-- omitiendo <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Agrandar texto</button>
</div>
</template>Gracias al oyente @enlarge-text="postFontSize += 0.1", el padre recibirá el evento y actualizará el valor de postFontSize.
Opcionalmente, podemos declarar los eventos emitidos utilizando la macro defineEmits:
vue
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>Esto documenta todos los eventos que un componente emite y opcionalmente los valida. También permite a Vue evitar aplicarlos implícitamente como oyentes nativos al elemento raíz del componente hijo.
Similar a defineProps, defineEmits solo se puede usar en <script setup> y no necesita ser importada. Devuelve una función emit que es equivalente al método $emit. Puede usarse para emitir eventos en la sección <script setup> de un componente, donde $emit no es directamente accesible:
vue
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>Ver también: Tipado de Emits de Componentes
Si no estás usando <script setup>, puedes declarar los eventos emitidos utilizando la opción emits. Puedes acceder a la función emit como una propiedad del contexto de setup (pasada a setup() como segundo argumento):
js
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}Eso es todo lo que necesitas saber sobre los eventos de componentes personalizados por ahora, pero una vez que hayas terminado de leer esta página y te sientas cómodo con su contenido, te recomendamos volver más tarde para leer la guía completa sobre Eventos Customizados.
Distribución de Contenido con Slots
Al igual que con los elementos HTML, a menudo es útil poder pasar contenido a un componente, así:
template
<AlertBox>
Algo malo sucedió.
</AlertBox>Lo que podría renderizar algo como:
Esto es un Error para Fines de Demostración
Algo malo sucedió.
Esto se puede lograr utilizando el elemento <slot> personalizado de Vue:
vue
<template>
<div class="alert-box">
<strong>Esto es un Error para Fines de Demostración</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>Como verás arriba, usamos el <slot> como un marcador de posición donde queremos que vaya el contenido, ¡y eso es todo! ¡Hemos terminado!
Eso es todo lo que necesitas saber sobre los slots por ahora, pero una vez que hayas terminado de leer esta página y te sientas cómodo con su contenido, te recomendamos volver más tarde para leer la guía completa sobre Slots.
Componentes Dinámicos
A veces, es útil cambiar dinámicamente entre componentes, como en una interfaz de pestañas:
Lo anterior es posible gracias al elemento <component> de Vue con el atributo especial is:
template
<!-- Los componentes cambian cuando cambia la pestaña actual -->
<component :is="tabs[currentTab]"></component>En el ejemplo anterior, el valor pasado a :is puede contener cualquiera de los siguientes:
- la cadena de nombre de un componente registrado, O
- el objeto de componente importado real
También puedes usar el atributo is para crear elementos HTML regulares.
Al cambiar entre múltiples componentes con <component :is="...">, un componente será desmontado cuando se cambie de él. Podemos forzar a que los componentes inactivos permanezcan "vivos" con el componente integrado <KeepAlive>.
Consideraciones sobre el Análisis de Templates en el DOM
Si estás escribiendo tus templates de Vue directamente en el DOM, Vue tendrá que recuperar la cadena de el template del DOM. Esto conlleva algunas consideraciones debido al comportamiento nativo de análisis HTML de los navegadores.
TIP
Cabe señalar que las limitaciones que se discuten a continuación solo se aplican si estás escribiendo tus templates directamente en el DOM. NO se aplican si estás utilizando templates de cadena de las siguientes fuentes:
- Componentes de Archivo Único
- Cadenas de template en línea (por ejemplo,
template: '...') <script type="text/x-template">
Insensibilidad a Mayúsculas y Minúsculas
Las etiquetas HTML y los nombres de atributos no distinguen entre mayúsculas y minúsculas, por lo que los navegadores interpretarán cualquier carácter en mayúscula como minúscula. Esto significa que cuando uses templates en el DOM, los nombres de componentes en PascalCase y los nombres de prop en camelCase o los nombres de eventos v-on deberán usar sus equivalentes en kebab-case (delimitados por guiones):
js
// camelCase en JavaScript
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}template
<!-- kebab-case en HTML -->
<blog-post post-title="hola!" @update-post="onUpdatePost"></blog-post>Etiquetas de Cierre Automático
Hemos estado utilizando etiquetas de cierre automático para componentes en ejemplos de código anteriores:
template
<MyComponent />Esto se debe a que el analizador de templates de Vue respeta /> como una indicación para finalizar cualquier etiqueta, independientemente de su tipo.
Sin embargo, en los templates en el DOM, siempre debemos incluir etiquetas de cierre explícitas:
template
<my-component></my-component>Esto se debe a que la especificación HTML solo permite que unos pocos elementos específicos omitan las etiquetas de cierre, siendo los más comunes <input> e <img>. Para todos los demás elementos, si omites la etiqueta de cierre, el analizador HTML nativo pensará que nunca terminaste la etiqueta de apertura. Por ejemplo, el siguiente fragmento:
template
<my-component /> <!-- pretendemos cerrar la etiqueta aquí... -->
<span>hola</span>se analizará como:
template
<my-component>
<span>hola</span>
</my-component> <!-- pero el navegador la cerrará aquí. -->Restricciones de Colocación de Elementos
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 causará problemas al usar componentes con elementos que tienen tales restricciones. Por ejemplo:
template
<table>
<blog-post-row></blog-post-row>
</table>El componente personalizado <blog-post-row> se extraerá como contenido no válido, causando errores en la salida renderizada final. Podemos usar el atributo especial is como solución alternativa:
template
<table>
<tr is="vue:blog-post-row"></tr>
</table>TIP
Cuando se utiliza en elementos HTML nativos, el valor de is debe ir prefijado con vue: para ser interpretado como un componente de Vue. Esto es necesario para evitar confusiones con los elementos customizados integrados nativos.
Eso es todo lo que necesitas saber sobre las consideraciones del análisis de templates en el DOM por ahora, y de hecho, el final de los Fundamentos de Vue. ¡Felicidades! Aún hay más que aprender, pero primero, te recomendamos tomar un descanso para jugar con Vue por tu cuenta: construye algo divertido, o consulta algunos de los Ejemplos si aún no lo has hecho.
Una vez que te sientas cómodo con los conocimientos que acabas de asimilar, continúa con la guía para aprender más a fondo sobre los componentes.