Postat acum 1 zi
typescript
type AsyncResult<T, E = Error> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: E };
// TypeScript îți forțează să tratezi fiecare caz
function render(r: AsyncResult<User>) {
switch (r.status) {
case "idle": return null;
case "loading": return <Spinner />;
case "success": return <UserCard user={r.data} />; // r.data există 100%
case "error": return <Error msg={r.error.message} />; // r.error există 100%
}
}De ce nu isLoading + data + error?
Pentru că e unsound — TypeScript nu te împiedică să faci:
if (isLoading) return <Spinner />;
return <UserCard user={data} />; // data poate fi undefined!
Cu Discriminated Union
Compilatorul știe că pe ramura success, data există. Pe ramura error, error există. Fără optional chaining, fără !, fără guard-uri manuale.
Bonus: exhaustive switch
Dacă adaugi o stare nouă (ex: "cancelled"), TypeScript îți spune exact unde te-ai rupt:
function render(r: AsyncResult<User>) {
switch (r.status) {
case "idle": return null;
case "loading": return <Spinner />;
case "success": return <UserCard user={r.data} />;
// ❌ Type error: "cancelled" e neacoperit
}
}
Folosesc ăsta în
- Rezultate
fetch/ React Query - State machine-uri mici
- Validare formular (pending / dirty / valid / invalid)
- Încărcare pagini server-side
Te forțează să gândești stările ca mutuellement exclusive, nu ca flags boolene care pot fi oricum combinate.