Skip to content

Estructura básica de un componente en Vue ​

1. ¿Qué es un componente? ​

Un componente es una parte reutilizable de una interfaz de usuario. Puede representar un botón, una tarjeta de producto, un formulario, una barra de navegación o incluso una página completa.

En Vue 3, un componente suele definirse en un archivo .vue, conocido como Single-File Component (SFC).

Un componente puede tener tres secciones principales:

vue
<script setup>
import { ref, computed } from 'vue';

// Lógica del componente
</script>

<template>
  <!-- Estructura visual del componente -->
</template>

<style scoped>
/* Estilos del componente */
</style>
  • <script setup>: contiene la lógica y los datos del componente.
  • <template>: define los elementos que se mostrarán en la interfaz.
  • <style scoped>: contiene los estilos CSS que se aplican a ese componente.

No todos los componentes necesitan las tres secciones. Por ejemplo, un componente sin estilos propios puede omitir <style>.

2. El bloque <script setup> ​

Esta sección permite escribir la lógica del componente utilizando JavaScript.

La palabra setup indica que Vue utiliza la sintaxis simplificada de la Composition API. Las variables y funciones declaradas en este bloque pueden utilizarse directamente en el template, sin necesidad de retornarlas manualmente.

vue
<script setup>
const nombre = 'Alex';

function saludar() {
  console.log(`Hola, ${nombre}`);
}
</script>

<template>
  <button @click="saludar">
    Saludar
  </button>
</template>

Al pulsar el botón, se ejecuta la función saludar().

3. Variables reactivas con ref() ​

En Vue, una variable reactiva permite que la interfaz se actualice cuando cambia su valor.

Para crear una referencia reactiva se utiliza ref():

vue
<script setup>
import { ref } from 'vue';

const contador = ref(0);

function incrementar() {
  contador.value++;
}
</script>

<template>
  <p>Contador: {{ contador }}</p>

  <button @click="incrementar">
    Incrementar
  </button>
</template>

¿Qué debes entender? ​

  • ref(0) crea una referencia reactiva cuyo valor inicial es 0.
  • En JavaScript, se accede al valor mediante contador.value.
  • En el template, Vue desenvuelve automáticamente las referencias en expresiones como .
  • Cuando cambia el valor, Vue actualiza la parte correspondiente de la interfaz.

Regla práctica: cuando utilizas una variable creada con ref() dentro del bloque <script setup>, normalmente debes acceder a su valor con .value.

4. Variables reactivas con reactive() ​

reactive() permite crear un objeto reactivo. Es útil cuando necesitas agrupar varias propiedades relacionadas.

vue
<script setup>
import { reactive } from 'vue';

const usuario = reactive({
  nombre: 'Alex',
  edad: 19,
  activo: true
});

function cumplirAnios() {
  usuario.edad++;
}
</script>

<template>
  <p>Nombre: {{ usuario.nombre }}</p>
  <p>Edad: {{ usuario.edad }}</p>

  <button @click="cumplirAnios">
    Cumplir años
  </button>
</template>

A diferencia de ref(), no necesitas escribir .value para acceder a las propiedades de un objeto creado con reactive().

Como regla general:

  • Usa ref() para valores individuales, arreglos u objetos cuando quieras una referencia reactiva.
  • Usa reactive() cuando quieras trabajar directamente con un objeto reactivo.

5. El bloque <template> ​

El template define la estructura de la interfaz. Vue permite utilizar HTML junto con expresiones y directivas especiales.

Interpolación con ​

Permite mostrar valores en la interfaz:

vue
<script setup>
const producto = 'Teclado';
const precio = 15000;
</script>

<template>
  <h2>{{ producto }}</h2>
  <p>Precio: ₡{{ precio }}</p>
</template>

Directiva v-if ​

Permite mostrar un elemento únicamente cuando se cumple una condición.

vue
<script setup>
import { ref } from 'vue';

const iniciadoSesion = ref(false);
</script>

<template>
  <p v-if="iniciadoSesion">
    Bienvenido al sistema.
  </p>

  <p v-else>
    Inicia sesión para continuar.
  </p>
</template>

Directiva v-for ​

Permite recorrer un arreglo y generar elementos para cada uno de sus registros.

vue
<script setup>
const productos = [
  { id: 1, nombre: 'Teclado' },
  { id: 2, nombre: 'Mouse' },
  { id: 3, nombre: 'Monitor' }
];
</script>

<template>
  <ul>
    <li
      v-for="producto in productos"
      :key="producto.id"
    >
      {{ producto.nombre }}
    </li>
  </ul>
</template>

La propiedad :key permite a Vue identificar cada elemento de forma estable cuando actualiza la lista. Lo habitual es utilizar un identificador único.

Directiva v-model ​

Permite enlazar un campo de formulario con una variable, manteniendo sincronizados el valor y la entrada del usuario.

vue
<script setup>
import { ref } from 'vue';

const nombre = ref('');
</script>

<template>
  <input v-model="nombre" placeholder="Tu nombre" />

  <p>Hola, {{ nombre }}</p>
</template>

Cuando el usuario escribe, el valor de nombre cambia y el texto se actualiza.

Directiva @click ​

Permite ejecutar una función cuando el usuario hace clic en un elemento.

vue
<script setup>
function mostrarMensaje() {
  alert('Botón pulsado');
}
</script>

<template>
  <button @click="mostrarMensaje">
    Pulsar
  </button>
</template>

El símbolo @ es una abreviatura de v-on:. Por ejemplo, @click equivale a v-on:click.

Directiva : ​

Los dos puntos son una abreviatura de v-bind: y permiten enlazar un atributo con una expresión de JavaScript.

vue
<script setup>
const imagen = '/images/producto.png';
const descripcion = 'Imagen del producto';
</script>

<template>
  <img :src="imagen" :alt="descripcion" />
</template>

En este caso, src y alt reciben los valores de las variables.

6. ¿Qué son las funciones en un componente? ​

Las funciones contienen instrucciones que se ejecutan cuando son llamadas. Se utilizan para responder a eventos, validar datos, realizar cálculos o ejecutar operaciones.

vue
<script setup>
import { ref } from 'vue';

const cantidad = ref(1);

function aumentarCantidad() {
  cantidad.value++;
}

function disminuirCantidad() {
  if (cantidad.value > 1) {
    cantidad.value--;
  }
}
</script>

<template>
  <button @click="disminuirCantidad">-</button>
  <span>{{ cantidad }}</span>
  <button @click="aumentarCantidad">+</button>
</template>

En este ejemplo, las funciones modifican una variable reactiva y Vue refleja los cambios en la interfaz.

Funciones con parámetros ​

Una función puede recibir argumentos para trabajar con diferentes valores.

vue
<script setup>
function saludar(nombre) {
  console.log(`Hola, ${nombre}`);
}
</script>

<template>
  <button @click="saludar('Alex')">
    Saludar
  </button>
</template>

Funciones asíncronas ​

Si una función necesita esperar una operación asíncrona, puede declararse con async y utilizar await.

vue
<script setup>
import { ref } from 'vue';

const usuarios = ref([]);
const cargando = ref(false);
const error = ref('');

async function cargarUsuarios() {
  cargando.value = true;
  error.value = '';

  try {
    const respuesta = await fetch('/api/usuarios');

    if (!respuesta.ok) {
      throw new Error('No se pudieron cargar los usuarios');
    }

    usuarios.value = await respuesta.json();
  } catch (e) {
    error.value = e.message;
  } finally {
    cargando.value = false;
  }
}
</script>

<template>
  <button @click="cargarUsuarios" :disabled="cargando">
    {{ cargando ? 'Cargando...' : 'Cargar usuarios' }}
  </button>

  <p v-if="error">{{ error }}</p>

  <ul>
    <li v-for="usuario in usuarios" :key="usuario.id">
      {{ usuario.nombre }}
    </li>
  </ul>
</template>

Este patrón es útil cuando el componente necesita consultar una API.

7. ¿Qué es computed()? ​

computed() permite crear un valor derivado de otros datos reactivos.

En lugar de almacenar manualmente un resultado que depende de otras variables, Vue lo calcula y lo mantiene actualizado cuando cambian sus dependencias.

vue
<script setup>
import { ref, computed } from 'vue';

const precio = ref(1000);
const cantidad = ref(3);

const total = computed(() => {
  return precio.value * cantidad.value;
});
</script>

<template>
  <p>Precio: ₡{{ precio }}</p>
  <p>Cantidad: {{ cantidad }}</p>
  <p>Total: ₡{{ total }}</p>
</template>

El resultado será 3000. Si cambia el precio o la cantidad, el total se actualizará automáticamente.

¿Cómo funciona? ​

  • computed() recibe una función que calcula el valor.
  • Vue detecta las dependencias reactivas que se leen dentro de esa función.
  • El resultado se actualiza cuando cambian esas dependencias.
  • El valor calculado se almacena en caché mientras sus dependencias no cambien.

¿Cuándo conviene usar computed()? ​

Cuando necesitas obtener un valor a partir de otros datos reactivos, por ejemplo:

  • El precio total de una compra.
  • El nombre completo de una persona.
  • Una lista filtrada de productos.
  • La cantidad de elementos que cumplen una condición.

Ejemplo de una lista filtrada:

vue
<script setup>
import { ref, computed } from 'vue';

const busqueda = ref('');

const productos = ref([
  'Teclado',
  'Mouse',
  'Monitor'
]);

const productosFiltrados = computed(() => {
  return productos.value.filter((producto) =>
    producto.toLowerCase().includes(busqueda.value.toLowerCase())
  );
});
</script>

<template>
  <input v-model="busqueda" placeholder="Buscar producto" />

  <ul>
    <li
      v-for="producto in productosFiltrados"
      :key="producto"
    >
      {{ producto }}
    </li>
  </ul>
</template>

La lista se filtra automáticamente cuando cambia el texto de búsqueda.

8. Diferencia entre una función normal y computed() ​

Aunque ambos pueden realizar cálculos, tienen propósitos diferentes.

CaracterísticaFunción normalcomputed()
PropósitoEjecutar una operación cuando se llama.Obtener un valor derivado de datos reactivos.
EjecuciónCuando se invoca.Cuando se necesita el resultado y cambian sus dependencias.
CachéNo tiene caché automática del resultado.Guarda el resultado mientras las dependencias no cambien.
Uso típicoEnviar un formulario, eliminar un registro o guardar datos.Calcular totales o filtrar listas.

Una función no es necesariamente menos eficiente; simplemente tiene otro comportamiento.

Regla práctica: si necesitas realizar una acción, normalmente utiliza una función. Si necesitas calcular un valor a partir de datos reactivos, considera computed().

9. ¿Qué es watch()? ​

watch() permite observar un dato reactivo y ejecutar una función cuando ese dato cambia.

A diferencia de computed(), que calcula un valor, watch() se utiliza para ejecutar efectos secundarios.

vue
<script setup>
import { ref, watch } from 'vue';

const busqueda = ref('');

watch(busqueda, (nuevoValor, valorAnterior) => {
  console.log('Antes:', valorAnterior);
  console.log('Ahora:', nuevoValor);
});
</script>

<template>
  <input v-model="busqueda" placeholder="Escribe algo" />
</template>

En este caso, cada vez que cambia busqueda, se ejecuta la función de observación.

Es útil para acciones como:

  • Consultar una API cuando cambia un filtro.
  • Guardar una preferencia cuando el usuario la modifica.
  • Sincronizar datos con otro sistema.

No necesitas utilizar watch() para todo. Si únicamente quieres mostrar un valor calculado, normalmente computed() es más apropiado.

10. ¿Qué son las propiedades props? ​

Las props permiten que un componente padre envíe datos a un componente hijo.

Por ejemplo, un componente de tarjeta puede recibir el nombre y el precio de un producto.

vue
<!-- ProductoCard.vue -->
<script setup>
defineProps({
  nombre: {
    type: String,
    required: true
  },
  precio: {
    type: Number,
    required: true
  }
});
</script>

<template>
  <article>
    <h2>{{ nombre }}</h2>
    <p>Precio: ₡{{ precio }}</p>
  </article>
</template>

El componente padre puede utilizarlo así:

vue
<ProductoCard nombre="Teclado" :precio="15000" />

En este ejemplo:

  • nombre se pasa como texto.
  • :precio enlaza una expresión de JavaScript, por lo que el valor se pasa como número.
  • defineProps() declara los datos que el componente espera recibir.

Las props deben tratarse como datos de solo lectura desde el componente hijo. Si el hijo necesita solicitar un cambio al padre, normalmente utiliza eventos.

11. ¿Qué son los eventos emit? ​

Los eventos permiten que un componente hijo comunique algo al componente padre.

vue
<!-- Contador.vue -->
<script setup>
import { ref } from 'vue';

const contador = ref(0);

const emit = defineEmits(['cambio']);

function incrementar() {
  contador.value++;
  emit('cambio', contador.value);
}
</script>

<template>
  <button @click="incrementar">
    Incrementar: {{ contador }}
  </button>
</template>

El padre puede escuchar el evento:

vue
<Contador @cambio="actualizarContador" />

En el padre, actualizarContador recibe el valor enviado por emit().

Este mecanismo permite separar la interfaz y la lógica de cada componente sin que el hijo tenga que modificar directamente los datos del padre.

12. El bloque <style scoped> ​

Esta sección contiene los estilos del componente.

vue
<style scoped>
button {
  padding: 8px 12px;
  border-radius: 6px;
  cursor: pointer;
}
</style>

El atributo scoped hace que los estilos se apliquen al componente correspondiente, en lugar de afectar de forma general a todos los elementos iguales de la aplicación. No es un aislamiento absoluto: existen detalles como estilos heredados, :deep() y contenido dinámico que pueden requerir atención.

13. Ejemplo completo ​

Este componente reúne variables reactivas, funciones, computed(), v-model, eventos y estilos.

vue
<script setup>
import { ref, computed } from 'vue';

const nombre = ref('');
const precio = ref(1000);
const cantidad = ref(1);

const total = computed(() => {
  return precio.value * cantidad.value;
});

function aumentarCantidad() {
  cantidad.value++;
}

function disminuirCantidad() {
  if (cantidad.value > 1) {
    cantidad.value--;
  }
}

function limpiarFormulario() {
  nombre.value = '';
  cantidad.value = 1;
}
</script>

<template>
  <section class="producto">
    <h2>Compra de producto</h2>

    <label for="nombre">Nombre del cliente</label>
    <input id="nombre" v-model="nombre" />

    <p>Precio unitario: ₡{{ precio }}</p>

    <div class="cantidad">
      <button @click="disminuirCantidad">-</button>
      <span>{{ cantidad }}</span>
      <button @click="aumentarCantidad">+</button>
    </div>

    <p>Total: ₡{{ total }}</p>

    <p v-if="nombre">
      Cliente: {{ nombre }}
    </p>

    <button @click="limpiarFormulario">
      Limpiar
    </button>
  </section>
</template>

<style scoped>
.producto {
  display: grid;
  gap: 12px;
  max-width: 350px;
}

.cantidad {
  display: flex;
  align-items: center;
  gap: 12px;
}
</style>

14. Resumen rápido ​

ElementoFunción principal
<script setup>Organiza la lógica del componente.
<template>Define la interfaz.
<style scoped>Define estilos locales.
ref()Crea una referencia reactiva.
reactive()Crea un objeto reactivo.
functionEjecuta una acción o procedimiento.
computed()Calcula un valor derivado y reactivo.
watch()Reacciona a cambios en datos observados.
v-ifMuestra elementos según una condición.
v-forRecorre una colección para renderizar elementos.
v-modelEnlaza datos con formularios.
@clickEscucha eventos de clic.
:atributoEnlaza atributos con expresiones.
propsRecibe datos del componente padre.
emitComunica eventos del hijo al padre.
async/awaitManeja operaciones asíncronas.

Idea clave: un componente Vue combina datos, lógica y presentación. Las variables reactivas mantienen la interfaz sincronizada con los datos, las funciones ejecutan acciones y computed() permite calcular valores que dependen de esos datos.