Vue.js 3 + TanStack Query
TanStack Query, el que conoces de React pero también disponible para Vue.js, es como darle a tu aplicación Vue.js 3 una superpotencia. Estas son las ventajas:
- Caching: dile adiós a llamadas innecesarias de API.
- Actualizaciones: manten tus datos frescos sin molestar a los usuarios.
- Manejo de errores: muestra correctamente mensajes de error a tus usuarios.
- Estado de carga: se acabaron los flags “
isLoading
”. - Paginación y scroll infinito: programa escenarios de datos complejos con facilidad.
Y la mejor parte es que funciona genial con composition API
.
Configuración de TanStack
Primeros tenemos que añadir TanStack a nuestro proyecto. Abre tu terminal, y en la raíz de tu proyecto ejecuta:
npm install --save @tanstack/vue-query
Ahora que tenemos el paquete instalado, es hora de configurar Tanstack Query en Vue.
// src/plugins/index.ts
import { VueQueryPlugin } from '@tanstack/vue-query'
import pinia from '../store'
export function registerPlugins(app) {
app
.use(router)
.use(pinia)
.use(VueQueryPlugin)
}
Podemos además configurar algunas opciones. Por ejemplo:
import { VueQueryPlugin } from '@tanstack/vue-query'
export function registerPlugins(app) {
// ... other plugins
app.use(VueQueryPlugin, {
queryClientConfig: {
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
},
},
},
})
}
Esta configuración establece un valor predeterminado staleTime
de 5 minutos para todas las consultas. Esto es como decirle a TanStack Query, “Hey, considera mis datos frescos durante 5 minutos”.
Para asegurarse de que todo está conectado correctamente, puedes añadir un pequeño log en algún componente principal:
<script setup lang="ts">
import { useQueryClient } from '@tanstack/vue-query'
const queryClient = useQueryClient()
console.log('TanStack Query is ready:', !!queryClient)
</script>
Refactorizando un fetching tradicional
Esta sería una implementación tradicional de un fetch en Vue:
<template>
<div>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<v-list v-else>
<v-list-item
v-for="articleItem in articlesList"
:key="articleItem.id"
:title="articleItem.title"
:value="articleItem.value"
>
<!-- Article item content -->
</v-list-item>
</v-list>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getArticles } from "@/services";
const articlesList = ref([]);
const isLoading = ref(false);
const error = ref(null);
const refreshArticles = async () => {
isLoading.value = true;
error.value = null;
try {
const response = await getArticles();
articlesList.value = response.data
} catch (err) {
error.value = err.message;
} finally {
isLoading.value = false;
}
};
onMounted(refreshArticles);
</script>
Refactorizamos con el siguiente código, donde tendremos que tener listo un método de consulta con fetch
o axios
que nos devuelva los datos de la API:
<template>
<div>
<div v-if="isPending">Loading...</div>
<div v-else-if="isError">Error: {{ error.message }}</div>
<v-list v-else>
<v-list-item
v-for="articleItem in data"
:key="articleItem.id"
:title="articleItem.title"
:value="articleItem.value"
>
<!-- Article item content -->
</v-list-item>
</v-list>
</div>
</template>
<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query';
import { getArticles } from "@/services";
const { isPending, isError, data, error } = useQuery({
queryKey: ['articles'],
queryFn: getArticles
},
});
</script>
useQuery
es un hook que necesita lo siguiente:
-
queryKey: Esto es como una id único para nuestra consulta. Aquí, estamos usando
['articles']
. TanStack Query utiliza esta clave para caché e invalidación. -
queryFn : Esta es nuestra función de fetching. TanStack Consulta llamará a esta función cuando necesite para buscar o volver a averiguar los datos.
Manejar estados de carga y errores
Como ves nuestro template se ha simplificado enormemente:
isPending
sustituye la referenciaisLoading
.isError
yerror
son proporcionados directamente por la consulta.- utilizamos
data
en nuestro v-for, sin usar nuevas referencias.
TanStack Query maneja todos estos estados para nosotros, haciendo que nuestro componente sea mucho más declarativo.
Adaptación de la IU para los Estados que consulta
Ahora que tenemos TanStack Query tarareando en nuestra aplicación Vue.js, vamos a pulir nuestra interfaz de usuario:
<template>
<div>
<v-skeleton-loader
v-if="isPending"
type="article, article, article"
:loading="isPending"
>
<v-card>Loading...</v-card>
</v-skeleton-loader>
<v-alert
v-else-if="isError"
type="error"
:title="error.name"
>
{{ error.message }}
</v-alert>
<v-list v-else>
<v-list-item
v-for="articleItem in data"
:key="articleItem.id"
:title="articleItem.title"
:value="articleItem.value"
>
<!-- Article item content -->
</v-list-item>
</v-list>
</div>
</template>
Manejar interacciones de usuarios
Vamos a añadir un filtro a nuestro componente:
<template>
<div>
<v-switch
v-model="filteredArticles"
label="Show only my articles"
@change="onArticlesFilter"
/>
<!-- ... rest of the template ... -->
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { getArticlesFiltered } from "@/services";
const filteredArticles = ref<boolean>(false);
const { isPending, isError, data, error } = useQuery({
queryKey: ['articles', filteredArticles.value ],
queryFn: () => getArticlesFiltered(filteredArticles.value),
});
function onArticlesFilter() {
// The query will automatically refetch when filteredArticles changes!
}
</script>
Reutilización de hooks
A medida que tu aplicación crece, puede que quieras reutilizar o simplemente sacar código de componentes que simplemente rendericen datos. Para ello puedes seguir algunas prácticas usuales como:
- Crear una carpeta de
hooks
. - Y para cada consulta un archivo, en este caso
useArticles.ts
:
// src/hooks/useArticles.ts
import { useQuery } from '@tanstack/vue-query';
import { getArticlesFiltered } from "@/services";
export function useArticles(filter:boolean) {
return useQuery({
queryKey: ['articles', filter],
queryFn: () => getArticlesFiltered(filter),
staleTime: 5 * 60 * 1000,
});
}
Ahora puedes usar este hook en cualquier componente:
<script setup lang="ts">
import { ref } from 'vue';
import { useArticles } from '@/hooks';
const filterArticles = ref<boolean>(false);
const { isPending, isError, data, error } = useArticles(filterArticles);
</script>
Optimizaciones
Aquí te dejo algunos consejos para exprimir aún más rendimiento:
-
staleTime
: Esto le dice a TanStack Query cuánto tiempo los datos deben considerarse frescos. Esto podemos usarlo para que los datos que no cambien a menudo. -
Prefetch
: Si sabes que es probable que el usuario necesite ciertos datos pronto, haz unprefetch
:const queryClient = useQueryClient(); queryClient.prefetchQuery(['articles', true ], () => getArticlesFiltered(true));
-
Paginación : Para grandes conjuntos de datos, utiliza
useInfiniteQuery
para implementar una paginación eficiente o scroll infinito.
Estrategias de refetch
TanStack Query hace refetch cuando falla alguna consulta por defecto, pero esto puede configurarse:
useQuery({
queryKey: ['articles'],
queryFn: getArticles,
// Se intentará la petición al menos 3 veces antes de mostrar un error
retry: 3,
});
Para un manejo de errores más específico, podemos utilizar el callback onError
:
useQuery({
queryKey: ['articles'],
queryFn: getArticles,
onError: (error) => {
if (error.response && error.response.status === 401) {
// manejar error, quizá podemos redirigir al usuario
}
},
});
Conclusiones
Como ves Tanstack Query simplifica mucho la forma en la que trabajamos con las interfaces y las consultas, quitando muchas de las operaciones que repetimos constantemente en millones de componentes y dejándonos un código limpio y sostenible, y sobre todo nos permite economizar mucho nuestras queries.