Arrêtez de modéliser l'état de l'UI avec des booléens
isLoading, isError, hasData - trois booléens décrivent huit
combinaisons possibles, et seulement quatre sont des états dans lesquels
votre composant devrait réellement se trouver. Les quatre autres sont des
bugs qui attendent qu'une condition réseau les déclenche.
Mauvais - des indicateurs indépendants qui peuvent se contredire
const isLoading = ref(false);
const isError = ref(false);
const data = ref<Invoice[] | null>(null);
async function load() {
isLoading.value = true;
isError.value = false;
try {
data.value = await api.get<Invoice[]>("/invoices");
} catch {
isError.value = true;
} finally {
isLoading.value = false;
}
}
Rien n'empêche isLoading et isError d'être vrais en même temps après
une course entre deux appels à load(). Le template finit avec des
chaînes de v-if qui vérifient les indicateurs dans un ordre précis pour
masquer des états qui n'auraient jamais dû être représentables.
Bon - un statut, une forme par statut
type RequestState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "error"; message: string }
| { status: "success"; data: T };
const state = ref<RequestState<Invoice[]>>({ status: "idle" });
async function load() {
state.value = { status: "loading" };
try {
state.value = { status: "success", data: await api.get<Invoice[]>("/invoices") };
} catch (e) {
state.value = { status: "error", message: toMessage(e) };
}
}
Le template fait un switch sur state.value.status, et TypeScript ne fait
exister data et message que là où ils sont valides - il n'y a aucune
branche où vous pouvez lire data pendant que status vaut "error".
Les combinaisons impossibles ne sont pas gérées, elles sont
irreprésentables.
Le compromis : une union discriminée demande plus de lecture que trois
lignes ref(false), et chaque consommateur doit faire un switch sur
status plutôt que vérifier un indicateur. Ça vaut le coup dès qu'un état
de chargement et un état d'erreur peuvent réellement se chevaucher en
pratique - ce qui est le cas la plupart du temps dès qu'un appel réseau
est impliqué.