
Loading Service em Vue/Nuxt 3
lgumas abordagens para um serviço global de loading em Vue/Nuxt e PrimeVue
layout: post title: "Loading Service em Vue/Nuxt 3" date: 2024-09-10 author: Catarina Roseta category: Ferramentas de Desenvolvimento tags: - vue3 - Nuxt3 - PrimeVue3 abstract: Algumas abordagens para um serviço global de loading em Vue/Nuxt e PrimeVue image: assets/img/posts/2024-09-10-use-loading.png
Para criar um serviço global de loading que permita gerir facilmente estados de loading e controlar um indicador de carregamento, podemos explorar diferentes abordagens, aqui utilizaremos Vue/Nuxt v3 com composition API, setup na tag script (<script setup>
) e o componente ProgressSpinner
da biblioteca PrimeVue v3.
Primeira Abordagem: Composable
A primeira abordagem envolve a criação de um composable que exporta um serviço de loading básico. Começaremos por criar um composable com lógica simples: um ref booleano isLoading
para gerir o estado global de loading e dois métodos, startLoading()
e stopLoading()
.
//useLoading.ts
import { ref } from 'vue';
export function useLoading() {
const isLoading = ref(false);
const startLoading = () => {
isLoading.value = true;
};
const stopLoading = () => {
isLoading.value = false;
};
return {
isLoading,
startLoading,
stopLoading,
};
}
Exemplo de Implementação num Componente:
//example.vue
<template>
<div>
<ProgressSpinner v-if="isLoading" aria-label="Loading" />
<Home v-if="!isLoading" :products="products" />
</div>
</template>
<script setup>
import { useLoading } from '~/composables/useLoading';
import { onMounted } from 'vue';
import Home from '~/components/Home.vue'; // Assuming Home component exists
const { startLoading, stopLoading, isLoading } = useLoading();
onMounted(async () => {
startLoading();
try {
// Simulating fetching data
const products = await fetchProducts();
} catch (error) {
console.error('Error fetching products:', error);
} finally {
stopLoading();
}
});
async function fetchProducts() {
// Simulating API call
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Product1', 'Product2']);
}, 2000);
});
}
</script>
Esta abordagem gere diretamente o estado de loading dentro do componente. Embora seja um pouco mais verbosa, encapsula a lógica central, separa as responsabilidades e é acessível globalmente. É um bom ponto de partida, mas podemos tornar mais eficiente.
### Segunda Abordagem: provide/inject Pattern
O pattern provide/inject
no Vue 3 permite partilhar estado entre um componente pai e os seus descendentes, passando dados e métodos pela árvore de componentes sem a necessidade de "prop drilling".
Vamos criar um composable useLoading.ts
usando o provide/inject pattern, juntamente com um componente LoadingOverlay
para representar a interface de loading.
Criação do composable useLoading.ts
//composables/useLoading.ts
import { ref, provide, inject } from 'vue';
interface LoadingContext {
isLoading: Ref<boolean>;
setLoading: (value: boolean) => void;
}
const LoadingSymbol = Symbol('loading');
export function provideLoading(): void {
const isLoading = ref(false);
function setLoading(value: boolean) {
isLoading.value = value;
}
provide(LoadingSymbol, {
isLoading,
setLoading,
});
}
export function useLoading(): LoadingContext {
const loading = inject(LoadingSymbol);
if (!loading) {
throw new Error('No loading provider found');
}
return loading as LoadingContext;
}
Criaçao do componente LoadingOverlay:
<template>
<div v-if="isLoading" class="flex justify-content-center>
<ProgressSpinner />
</div>
</template>
<script setup>
import { useLoading } from '~/composables/useLoading';
const { isLoading } = useLoading();
</script>
Implementação do useLoading
num Componente Filho:
<template>
<Home v-if="!isLoading && products.length" :products="products" />
</template>
<script setup>
import { useLoading } from '~/composables/useLoading';
const { setLoading, isLoading } = useLoading();
// Simulating fetch logic
onMounted(async () => {
try {
setLoading(true);
const products = await fetchProducts();
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
});
async function fetchProducts() {
// Simulating API call
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Product1', 'Product2']);
}, 2000);
});
}
</script>
Fornecendo o Estado de Loading num Componente de Nível Superior (por exemplo, app.vue)
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup>
provideLoading();
</script>
Esta abordagem garante que qualquer componente possa acessar o estado global de loading usando o useLoading()
.
Terceira Abordagem: Broadcasting de Eventos
Se precisar de mais flexibilidade para transmitir o estado de loading entre componentes que não são pai/filho, pode usar um event bus como mitt. Isto permite que componentes não relacionados ouçam alterações no estado de loading sem passar dados pela árvore de componentes.
Comece por instalar a biblioteca mitt:
`npm install mitt`
`yarn add mitt`
`pnpm add mitt`
Criar um Serviço Global de Loading (useLoadingBroadcast.ts
):
//composables/useLoadingBroadcast.ts
import { ref } from 'vue';
import mitt from 'mitt';
type LoadingEvents = {
'loading:change': boolean;
};
const emitter = mitt<LoadingEvents>();
const isLoading = ref(false);
export function useLoadingBroadcast() {
function setLoading(value: boolean) {
isLoading.value = value;
emitter.emit('loading:change', value);
}
function onLoadingChange(callback: (value: boolean) => void) {
emitter.on('loading:change', callback);
}
function offLoadingChange(callback: (value: boolean) => void) {
emitter.off('loading:change', callback);
}
return {
isLoading,
setLoading,
onLoadingChange,
offLoadingChange,
};
}
Criar um Componente LoadingOverlay
:
//loading-overlay.vue
<template>
<div class="loading-overlay" v-if="isLoading">
<ProgressSpinner />
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useLoadingBroadcast } from '~/composables/useLoadingBroadcast';
const { onLoadingChange, offLoadingChange } = useLoadingBroadcast();
const isLoading = ref(false);
const handleLoadingChange = (value) => {
isLoading.value = value;
};
onMounted(() => {
onLoadingChange(handleLoadingChange);
});
onUnmounted(() => {
offLoadingChange(handleLoadingChange);
});
</script>
Usar LoadingOverlay
num Componente de Nível Superior (por exemplo, no layout default.vue):
//default.vue
<template>
<LoadingOverlay />
<slot />
</template>
Implementação num Componente Filho:
//example.vue
<template>
<div>
<Home v-if="!isLoading" :products="products" />
</div>
</template>
<script setup>
import { useLoadingBroadcast } from '~/composables/useLoadingBroadcast';
import { onMounted } from 'vue';
import Home from '~/components/Home.vue'; // Assuming Home component exists
const { setLoading, isLoading } = useLoadingBroadcast();
onMounted(async () => {
try {
setLoading(true);
const products = await fetchProducts();
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
});
async function fetchProducts() {
// Simulating API call
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Product1', 'Product2']);
}, 2000);
});
}
</script>
Este setup garante que:
- A função
setLoading
emite um evento quando chamada. - O
LoadingOverlay
ouve esses eventos e atualiza o estado de loading de acordo. - Qualquer componente pode controlar o estado global de loading.
Conclusão
Implementar um serviço global de loading no Vue/Nuxt 3 pode ser feito de várias maneiras, dependendo da complexidade e estrutura da sua aplicação. A abordagem Composable é ideal para uso em pequena escala com overhead mínimo, enquanto o pattern Provide/Inject oferece uma solução mais escalável para árvores de componentes pai-filho. Para maior flexibilidade, especialmente ao lidar com componentes não relacionados, a abordagem de Broadcasting de Eventos usando mitt fornece uma maneira eficiente de gerir o estado de loading em toda a aplicação. Cada método tem as suas vantagens, permitindo adaptar a solução às necessidades do projeto.