Todos los artículos

¿Qué es un Composable?

Extrae consultas HTTP a un composable tipado que cancela peticiones anteriores y maneja errores.

LA

Luis Aldair Quispe Rios

3 min de lectura
Descubre qué son los composables en Vue.js con esta representación visual de piezas de rompecabezas de colores vibrantes, simbolizando la encapsulación de lógica, la modularidad y la reutilización de código. Aprende cómo los composables optimizan el desarrollo web moderno.

Qué es un composable

Un composable es una función que usa la Composition API para encapsular lógica con estado y reutilizarla. No es solo una función auxiliar: puede crear referencias, reaccionar a cambios y limpiar efectos cuando el componente se desmonta.

Objetivo y requisitos

Crearemos useQuery, un composable que recibe una URL fija, una referencia o un getter. Necesitas Vue 3.3 o superior por toValue, TypeScript y un navegador con fetch y AbortController. El servidor debe devolver JSON con la forma que espera tu aplicación.

useQuery.ts completo

Guárdalo en src/composables/useQuery.ts:

import { ref, shallowRef, toValue, watchEffect } from 'vue'
import type { MaybeRefOrGetter } from 'vue'

export function useQuery<T>(url: MaybeRefOrGetter<string>) {
  const data = shallowRef<T | null>(null)
  const error = ref<string | null>(null)
  const isLoading = ref(false)

  watchEffect((onCleanup) => {
    // La lectura ocurre antes de cualquier await para registrar la dependencia.
    const currentUrl = toValue(url)
    const controller = new AbortController()
    let active = true

    onCleanup(() => {
      active = false
      controller.abort()
    })

    data.value = null
    error.value = null
    isLoading.value = false
    if (!currentUrl) return
    isLoading.value = true

    async function load() {
      try {
        const response = await fetch(currentUrl, { signal: controller.signal })
        if (!response.ok) throw new Error(`HTTP ${response.status}`)
        const result = (await response.json()) as T
        if (active) data.value = result
      } catch (cause: unknown) {
        if (active && !controller.signal.aborted) {
          error.value = cause instanceof Error ? cause.message : 'No se pudo cargar'
        }
      } finally {
        if (active) isLoading.value = false
      }
    }

    void load()
  })

  return { data, error, isLoading }
}

La limpieza cancela la solicitud anterior y la marca como inactiva. Ese segundo control evita que una respuesta tardía reemplace los datos actuales. Comprobamos response.ok porque fetch no rechaza una promesa solo por recibir un HTTP 404 o 500. El tipo T describe el resultado esperado; no valida la respuesta del servidor en tiempo de ejecución.

Uso completo en App.vue

Para probarlo utilizaremos una API pública de demostración. No envíes información personal a este ejemplo.

<script setup lang="ts">
import { ref } from 'vue'
import { useQuery } from './composables/useQuery'

type Todo = { id: number; title: string; completed: boolean }
const todoId = ref(1)
const { data, error, isLoading } = useQuery<Todo>(
  () => `https://jsonplaceholder.typicode.com/todos/${todoId.value}`,
)
</script>

<template>
  <label>Tarea
    <select v-model.number="todoId">
      <option :value="1">Primera</option>
      <option :value="2">Segunda</option>
      <option :value="99999">Inexistente</option>
    </select>
  </label>
  <p v-if="isLoading" role="status">Cargando…</p>
  <p v-else-if="error" role="alert">{{ error }}</p>
  <p v-else-if="data">{{ data.id }}: {{ data.title }}</p>
</template>

Comprueba el resultado

Al iniciar se carga la tarea 1. Cambia rápidamente a la 2 con la red ralentizada en las herramientas del navegador: al terminar debe verse la tarea 2. Selecciona la tarea inexistente para comprobar el error HTTP y vuelve a una válida para recuperarte. Al desmontar el componente se cancela la solicitud pendiente. Ejecuta también npm run build.

Errores frecuentes

Pasar todoId.value dentro de una URL ya construida entrega una cadena fija: no cambia cuando cambia la referencia. Usa un getter como en el ejemplo. Llama al composable de forma sincrónica dentro de setup para asociar sus efectos al componente. Cada invocación crea su propio estado; compartir resultados o añadir caché requiere un diseño adicional.

Para un caso con debounce y un servidor de prueba, continúa con watch vs. watchEffect: un cotizador de envíos. Lee también Composables y Watchers en Vue.

Sigue leyendo

watch vs. watchEffect en Vue 3: evita cotizaciones de envío desactualizadas

watch vs. watchEffect en Vue 3: evita cotizaciones de envío desactualizadas

Evita respuestas HTTP desactualizadas con watch, debounce y cancelación en un cotizador de envíos.
Autenticación con Google en Vue 3 y VueFire

Autenticación con Google en Vue 3 y VueFire

Conecta Firebase Auth con VueFire y añade acceso con Google, cierre de sesión y errores visibles.