Te guste o no, la API de Composición es el nuevo estándar de Vue.js. Como ocurre con cualquier novedad importante, ya sea por falta de atención o de documentación, su adopción puede llevar a cometer errores que con el tiempo se convierten en malas prácticas o ejemplos de uso incorrecto.
En este DevTip, examinaremos tres de estos errores comunes: cuáles son, por qué ocurren y cómo puedes solucionarlos de una vez por todas.
Problema 1: Uso incorrecto de reactive
Este es un clásico y uno de los motivos por los que algunos desarrolladores Vue prefieren usar siempre ref
, en lugar de reactive
.
Primero, no podemos usar valores primitivos con reactive
, solo objetos como Object
, Array
, Map
y demás. Segundo, una vez creada la referencia, no se puede romper. Esto quiere decir que esa referencia no debe cambiar, pero sí mutar.
Por último, y como consecuencia de ello, no se puede desestructurar reactive
sin un poco de ayuda extra. Me explico:
import { reactive } from 'vue';
const state = reactive({
count: 0,
name: 'John',
});
const { name } = state;
state.name = 'Juan';
console.log(name, state.name); // John Juan
Como ves, una vez realizamos la desestructuración se pierde la referencia y por ende name
no se actualiza.
Para desestructurar reactive
puedes usar el helper toRefs
, que convertirá las propiedades un objeto creado con reactive
en referencias reactivas (generadas con ref
).
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
name: 'John',
});
const { name } = toRefs(state);
state.name = 'Juan';
console.log(name.value, state.name); // Juan Juan
Como ves, ahora name
es una referencia reactiva enlazada al objeto reactivo. En realidad ahora es un objeto ya que se accede a través de value
. Mientras esto siga siendo así, siempre se mantendrán sicronizadas.
Problema 2: Demasiada lógica en tus componentes
Uno de los aspectos más positivos de la Options API es que nos fuerza a estructurar la lógica en diferentes partes. Gracias a ella, tu código resulta más organizado. Al dar el paso a la Composition API, nuestro código puede parecer desorganizado, ya que ahora tenemos mayor libertad. Ahora cae en nosotros la responsabilidad de seguir un patrón de estructura de código.
Otro (bajo mi punto de vista) error es agrupar un montón de lógica relacionada en un componente. Muchas de las veces olvidamos que ahora podemos usar las API reactivas (ref
, reactive
y demás) fuera de ellos.
Un ejemplo:
import { ref, reactive, onMounted, watch } from 'vue';
const data = ref(null);
const error = ref(null);
const searchTerm = ref('');
const state = reactive({ count: 0 });
const fetchData = async () => {
try {
const response = await fetch(
`https://api.example.com/data?search=${searchTerm.value}`
);
data.value = await response.json();
} catch (err) {
error.value = err;
}
};
onMounted(fetchData);
watch(searchTerm, fetchData);
No hay nada negativo en el código de arriba, pero en la mayoría de ocasiones es interesante extraer funciones y lógica que pueda ser reutilizada en otras partes. En este caso, la función fetchData
se convierte en el composable useFetchData
:
import { ref, onMounted, watch } from 'vue';
export function useFetchData(searchTerm) {
const data = ref(null);
const error = ref(null);
const fetchData = async () => {
try {
const response = await fetch(
`https://api.example.com/data?search=${searchTerm.value}`
);
data.value = await response.json();
} catch (err) {
error.value = err;
}
};
onMounted(fetchData);
watch(searchTerm, fetchData);
return { data, error };
}
Utilizarlo es tan sencillo como:
import { useFetchData } from '@/composables';
const searchTerm = ref('');
const { data, error } = useFetchData(searchTerm);
Problema 3: Esperar reactividad cuando no es posible
Por último, pero no menos importante, tenemos aquellas situaciones en las que se espera que algo siga siendo reactivo cuando en realidad ya no lo es. Esta falsa asunción hace que este tipo de errores sea los más difíciles de depurar.
Supongamos que queremos pasar parte de las propiedades reactivas de un componente a otro lugar —como un composable— y esperar que sigan siendo reactivas.
Un ejemplo supersimplificado:
import { watch } from 'vue';
export function useTest(msg) {
watch(
msg,
(newVal) => {
console.log(newVal);
},
{ immediate: true }
);
}
Ahora hacemos uso del composable useTest
en nuestro componente:
<script setup>
import { useTest } from '@/composables/useTest'
const props = defineProps<{
msg: string
}>()
useTest(props.msg)
</script>
¿Ves el problema?. A simple vista no es sencillo.
El problema es que props.msg
no es una referencia reactiva, es el resultado de ella. El valor final. Igual que si hicieras referencia a la propiedad value
de algo creado con ref
. Lo que estás pasando a useTest
es un string
, no un objeto.
De hecho, si intentas poner en uso el código de arriba Vue mismo te dará un mensaje de advertencia:
Vue warn]: Invalid watch source: You did it! A watch source can only be a
getter/effect function, a ref, a reactive object, or an array of these types.
¿Qué podemos hacer al respecto?. En realidad ya lo sabes: toRefs
. De nuevo. De esta forma creas una referencia reactiva (de nuevo, con ref
) usando el valor de props.msg
:
<script setup lang="ts">
import { toRefs } from "vue"
...
const { msg } = toRefs(props)
useTest(msg)
</script>
Ahora todo funciona como te imaginas, y cualquier cambio en props.msg
desatará la reactividad, siendo useTest
notificado de la mutación.
Conclusiones
En resumen, el uso de la API de Composición en Vue.js puede ser un gran cambio, pero también puede aportar muchos beneficios en términos de flexibilidad y capacidad para reutilizar lógica. Sin embargo, como con cualquier herramienta, es importante usarla correctamente para evitar caer en trampas comunes y errores.
Al entender cómo manejar adecuadamente los objetos reactivos, estructurar tu código de manera eficiente y manejar la reactividad, puedes aprovechar al máximo esta poderosa característica de Vue.js.
Si estás interesado en aprenderla, dale un vistazo al Taller de Composition API.