tutoriales.com

Migrando de Options API a Composition API en Vue 3: Una Guía Práctica

Este tutorial te guiará a través del proceso de migración de componentes Vue 3 desde la Options API tradicional a la moderna Composition API. Exploraremos los beneficios clave, proporcionaremos ejemplos paso a paso y te equiparemos con las herramientas necesarias para refactorizar tus aplicaciones de manera eficiente y escalable.

Intermedio15 min de lectura22 views
Reportar error

La Composition API se introdujo en Vue 3 como una alternativa más flexible y potente a la Options API para organizar la lógica de los componentes. Mientras que la Options API agrupa la lógica por tipo (data, methods, computed), la Composition API permite agrupar la lógica por característica, mejorando la legibilidad y mantenibilidad, especialmente en componentes grandes y complejos.

🚀 ¿Por qué migrar a Composition API?

La Composition API no es solo una alternativa; ofrece ventajas significativas que la hacen una elección preferida para el desarrollo moderno en Vue 3:

Mejor organización del código

En la Options API, componentes grandes pueden llevar a un "pozo de opciones" donde la lógica de una misma característica está dispersa en data, methods, computed, y watch. La Composition API permite agrupar toda la lógica relacionada con una característica específica dentro de una función setup, o incluso en funciones composable separadas.

Options API Composition API Componente Vue data() methods computed watch Lógica fragmentada por tipo de opción Componente Vue setup() Funcionalidad A const state = ref(...) function action() { ... } computed(() => ...) Funcionalidad B const search = reactive(...) watch(search, () => ...) function reset() { ... } Lógica agrupada por funcionalidad

Mayor reusabilidad y abstracción

Con la Composition API, puedes extraer piezas de lógica reactiva en funciones composable reutilizables. Estas funciones pueden ser compartidas entre múltiples componentes, promoviendo la abstracción y reduciendo la duplicación de código de una manera mucho más limpia que los mixins de la Options API.

Mejor inferencia de tipos con TypeScript

La Composition API es mucho más amigable con TypeScript. Su estructura basada en funciones permite una mejor inferencia de tipos, lo que resulta en un código más seguro y un mejor soporte de autocompletado en tu IDE.

Rendimiento mejorado (en algunos casos)

Aunque el rendimiento no es el objetivo principal de la Composition API, su estructura puede llevar a un código más eficiente y optimizable para el compilador de Vue, especialmente en el contexto de la reactividad.

💡 Consejo: La migración no siempre es obligatoria. Si tienes componentes pequeños y simples que funcionan bien con Options API, no hay necesidad urgente de cambiarlos. Prioriza la migración de componentes grandes o aquellos que necesitan nuevas funcionalidades.

🛠️ Entendiendo los fundamentos: Options API vs. Composition API

Antes de sumergirnos en la migración, repasemos las diferencias fundamentales.

Options API

En la Options API, defines las propiedades del componente en un objeto de opciones:

// Componente con Options API
export default {
  data() {
    return {
      count: 0,
      message: 'Hola Vue!'
    };
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('');
    }
  },
  methods: {
    increment() {
      this.count++;
    },
    greet() {
      alert(this.message);
    }
  },
  mounted() {
    console.log('Componente montado con Options API');
  }
};

Composition API

En la Composition API, la lógica se centraliza en la función setup(). Usas ref y reactive para la reactividad, y computed, watch, onMounted, etc., para funcionalidades análogas a sus contrapartes en Options API, pero importándolas directamente desde Vue.

// Componente con Composition API
import { ref, computed, onMounted } from 'vue';

export default {
  setup() {
    // Estado reactivo
    const count = ref(0);
    const message = ref('Hola Vue!');

    // Propiedad computada
    const reversedMessage = computed(() => {
      return message.value.split('').reverse().join('');
    });

    // Métodos
    const increment = () => {
      count.value++;
    };
    const greet = () => {
      alert(message.value);
    };

    // Ciclo de vida
    onMounted(() => {
      console.log('Componente montado con Composition API');
    });

    // Devolver lo que se expondrá a la plantilla
    return {
      count,
      message,
      reversedMessage,
      increment,
      greet
    };
  }
};
🔥 Importante: Recuerda que en Composition API, para acceder al valor de una ref, debes usar `.value` (ej. `count.value`), mientras que para `reactive` no es necesario si accedes directamente a sus propiedades.

🎯 Estrategias de Migración Paso a Paso

Migrar un componente puede parecer intimidante, pero con un enfoque sistemático, es manejable.

1. Preparación y Análisis del Componente

Antes de escribir código, tómate un momento para entender el componente actual:

  • Identifica las características: Agrupa la lógica existente por funcionalidad. Por ejemplo, si tienes un componente de ProductDetails, podrías tener características como gestiónDeCantidad, manejoDeFavoritos, cargaDeDatosDelProducto.
  • Dependencias: ¿Qué props recibe? ¿Qué eventos emite? ¿Usa Vuex/Pinia? ¿Utiliza mixins?
  • Pruebas: Si el componente tiene pruebas unitarias o de integración, asegúrate de que pasen antes de la migración. Las pruebas son tu red de seguridad.

2. Creación de la función setup()

El primer paso es añadir la función setup() a tu componente. Esta función será el corazón de tu lógica en Composition API.

// Antes
export default {
  data() { /* ... */ },
  methods: { /* ... */ }
};

// Después (estructura inicial)
import { /* imports de Vue */ } from 'vue';

export default {
  setup() {
    // Aquí irá toda la lógica

    return { /* ... */ }; // Lo que se expone a la plantilla
  }
};

3. Migración de data a ref o reactive

Las propiedades de data se convierten en ref (para valores primitivos y objetos simples que se reemplazarán) o reactive (para objetos complejos que se mutarán internamente).

// Options API data
data() {
  return {
    count: 0,
    user: { id: 1, name: 'Alice' },
    items: []
  }
}

// Composition API equivalent
import { ref, reactive } from 'vue';

// Dentro de setup()
const count = ref(0);
const user = reactive({ id: 1, name: 'Alice' }); // O ref({ id: 1, name: 'Alice' })
const items = ref([]); // Array, por lo general con ref

// Al devolver:
return { count, user, items };
📌 Nota: Usa `ref` para valores primitivos y cuando necesites reemplazar un objeto completo. Usa `reactive` para objetos y arrays si planeas mutar sus propiedades internas.

4. Migración de computed a computed (de Vue)

Las propiedades computadas se migran usando la función computed importada de Vue.

// Options API computed
computed: {
  fullName() {
    return this.user.firstName + ' ' + this.user.lastName;
  }
}

// Composition API equivalent
import { ref, computed, reactive } from 'vue';

// Dentro de setup()
const user = reactive({ firstName: 'John', lastName: 'Doe' });

const fullName = computed(() => {
  return user.firstName + ' ' + user.lastName; // No '.value' si 'user' es reactive
});

// Si 'user' fuera un ref de un objeto:
// const user = ref({ firstName: 'John', lastName: 'Doe' });
// const fullName = computed(() => { return user.value.firstName + ' ' + user.value.lastName; });

return { user, fullName };

5. Migración de methods a funciones

Los métodos se convierten simplemente en funciones JavaScript regulares dentro de setup().

// Options API methods
methods: {
  increment() {
    this.count++;
  },
  changeName(newName) {
    this.user.name = newName;
  }
}

// Composition API equivalent
import { ref, reactive } from 'vue';

// Dentro de setup()
const count = ref(0);
const user = reactive({ name: 'Alice' });

const increment = () => {
  count.value++;
};
const changeName = (newName) => {
  user.name = newName;
};

return { count, user, increment, changeName };

6. Migración de watch a watch y watchEffect

El observador (watch) se migra usando la función watch importada de Vue, y en algunos casos, watchEffect.

// Options API watch
watch: {
  count(newValue, oldValue) {
    console.log(`Count cambió de ${oldValue} a ${newValue}`);
  },
  'user.name'(newName, oldName) {
    console.log(`El nombre del usuario cambió de ${oldName} a ${newName}`);
  }
}

// Composition API equivalent
import { ref, watch, reactive } from 'vue';

// Dentro de setup()
const count = ref(0);
const user = reactive({ name: 'Alice' });

watch(count, (newValue, oldValue) => {
  console.log(`Count cambió de ${oldValue} a ${newValue}`);
});

// Para propiedades anidadas de objetos reactive, usa una función getter
watch(() => user.name, (newName, oldName) => {
  console.log(`El nombre del usuario cambió de ${oldName} a ${newName}`);
});

// watchEffect: ejecuta un efecto inmediatamente y lo re-ejecuta cada vez que sus dependencias reactivas cambian
// watchEffect(() => {
//   console.log(`El count actual es: ${count.value}`);
//   // Esto se ejecutará cada vez que count.value cambie
// });

return { count, user };

7. Migración de lifecycle hooks a onMounted, onUpdated, etc.

Los hooks del ciclo de vida se migran utilizando sus contrapartes con prefijo on importadas de Vue.

💡 Consejo: La palabra `this` ya no se usa en `setup()`. Todos los valores reactivos deben ser accedidos directamente (ej. `count.value`) o desestructurados de un objeto `reactive`.
Options API HookComposition API Hook
------
beforeCreate(Ya no es necesario, setup() lo reemplaza)
created(Ya no es necesario, setup() lo reemplaza)
------
beforeMountonBeforeMount
mountedonMounted
------
beforeUpdateonBeforeUpdate
updatedonUpdated
------
beforeUnmountonBeforeUnmount
unmountedonUnmounted
------
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
------
renderTriggeredonRenderTriggered
activatedonActivated
------
deactivatedonDeactivated
// Options API hooks
export default {
  mounted() {
    console.log('Componente montado!');
  },
  beforeUnmount() {
    console.log('Componente a punto de ser desmontado');
  }
};

// Composition API equivalent
import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Componente montado con Composition API!');
    });

    onBeforeUnmount(() => {
      console.log('Componente a punto de ser desmontado con Composition API');
    });

    return {};
  }
};

8. Manejo de props y emits

En setup(), las props se reciben como el primer argumento, y la función emit como el segundo. Para hacer las props reactivas, debes usar toRefs si necesitas desestructurarlas mientras mantienes la reactividad.

// Options API props y emits
export default {
  props: {
    initialValue: Number,
    label: String
  },
  emits: ['update:value'],
  data() {
    return { currentValue: this.initialValue };
  },
  methods: {
    updateValue() {
      this.$emit('update:value', this.currentValue + 1);
    }
  }
};

// Composition API equivalent
import { ref, toRefs } from 'vue';

export default {
  props: {
    initialValue: Number,
    label: String
  },
  emits: ['update:value'],
  setup(props, { emit }) {
    // Para acceder a props individuales con reactividad
    const { initialValue } = toRefs(props);

    const currentValue = ref(initialValue.value);

    const updateValue = () => {
      currentValue.value++;
      emit('update:value', currentValue.value);
    };

    return { currentValue, updateValue, label: props.label };
  }
};
¿Cuándo usar `toRefs`?Usa `toRefs` cuando desestructuras props de un objeto `props` para que las propiedades desestructuradas mantengan su reactividad. Si accedes a `props.someProp` directamente, no necesitas `toRefs`, pero si haces `const { someProp } = props;`, `someProp` perderá la reactividad. `toRefs` resuelve esto creando refs para cada propiedad desestructurada.

9. Extracción de Lógica con Composables

Este es uno de los mayores beneficios de la Composition API. Identifica bloques de lógica que puedan ser reutilizados y extráelos a funciones composable separadas.

Ejemplo: Un composable para un contador.

// src/composables/useCounter.js
import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  const isEven = computed(() => count.value % 2 === 0);

  return { count, increment, decrement, isEven };
}

Uso en un componente:

// Componente MyCounter.vue
import { useCounter } from '@/composables/useCounter';

export default {
  setup() {
    const { count, increment, decrement, isEven } = useCounter(10);

    return {
      count,
      increment,
      decrement,
      isEven
    };
  }
};
⚠️ Advertencia: Evita sobre-componer componentes pequeños. La Composition API es más útil en componentes con lógica compleja o repetitiva.

✅ Ejemplo de Migración Completa

Vamos a tomar un componente típico de Options API y migrarlo por completo.

Componente original (Options API)

Consideremos un componente ProductCard que muestra detalles de un producto, tiene un contador de cantidad y permite marcarlo como favorito.

// ProductCard.vue (Options API)
export default {
  props: {
    product: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      quantity: 1,
      isFavorite: false
    };
  },
  computed: {
    totalPrice() {
      return this.product.price * this.quantity;
    }
  },
  methods: {
    addToQuantity() {
      this.quantity++;
    },
    subtractFromQuantity() {
      if (this.quantity > 1) {
        this.quantity--;
      }
    },
    toggleFavorite() {
      this.isFavorite = !this.isFavorite;
    }
  },
  watch: {
    quantity(newQty) {
      console.log(`Cantidad del producto ${this.product.name}: ${newQty}`);
    }
  },
  mounted() {
    console.log(`ProductCard para ${this.product.name} montado.`);
  }
};

Migración a Composition API

Primero, definiremos un composable para la lógica de cantidad y otro para el estado de favorito.

// src/composables/useQuantity.js
import { ref, computed } from 'vue';

export function useQuantity(initialQty = 1, price) {
  const quantity = ref(initialQty);

  const addToQuantity = () => {
    quantity.value++;
  };

  const subtractFromQuantity = () => {
    if (quantity.value > 1) {
      quantity.value--;
    }
  };

  const totalPrice = computed(() => price.value * quantity.value);

  return { quantity, addToQuantity, subtractFromQuantity, totalPrice };
}

// src/composables/useFavorite.js
import { ref } from 'vue';

export function useFavorite(initialState = false) {
  const isFavorite = ref(initialState);

  const toggleFavorite = () => {
    isFavorite.value = !isFavorite.value;
  };

  return { isFavorite, toggleFavorite };
}

Ahora, el componente ProductCard.vue con Composition API:

// ProductCard.vue (Composition API)
import { toRefs, onMounted, watch, computed } from 'vue';
import { useQuantity } from '@/composables/useQuantity';
import { useFavorite } from '@/composables/useFavorite';

export default {
  props: {
    product: {
      type: Object,
      required: true
    }
  },
  setup(props) {
    // Desestructurar props con toRefs para mantener reactividad
    const { product } = toRefs(props);

    // Usar composables para la lógica
    const { quantity, addToQuantity, subtractFromQuantity, totalPrice } =
      useQuantity(1, computed(() => product.value.price)); // Pasar precio como computado para reactividad

    const { isFavorite, toggleFavorite } = useFavorite();

    // Watchers
    watch(quantity, (newQty) => {
      console.log(`Cantidad del producto ${product.value.name}: ${newQty}`);
    });

    // Lifecycle hooks
    onMounted(() => {
      console.log(`ProductCard para ${product.value.name} montado con Composition API.`);
    });

    // Devolver todas las propiedades y métodos para la plantilla
    return {
      product: product.value, // O pasar product directamente si no se desestructura en la plantilla
      quantity,
      addToQuantity,
      subtractFromQuantity,
      totalPrice,
      isFavorite,
      toggleFavorite
    };
  }
};
🔥 Importante: Cuando pasas `product: product.value` en el `return` del `setup`, estás desenvolviendo el ref y el objeto `product` ya no será reactivo si se reasigna la prop `product` desde el padre. Si el `product` puede cambiar, devuelve `product` directamente (que es un ref) y accede a `product.value.name` en la plantilla, o usa `

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!