TypeScript har mange kule features som gjør hverdagen enklere, særlig på prosjekter der man er flere utviklere, eller der det går en stund mellom hver gang man er innom kodebasen.
En av dem er discriminated unions. I dag tenkte jeg å vise hvordan du kan bruke de til å skrive lesbare og typesikre komponenter! ✨
Hvis man har skrevet JavaScript og React før, så har man garantert sett dette mønstret gå igjen i mange komponenter:
const Komponent = () => {
const [data, setData] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [hasErrored, setHasErrored] = useState(false);
useEffect(() => {
getData().then((res) => {
setData(res);
}).catch((err) => {
setHasErrored(true);
});
}, []);
if (isLoading) {
return <Spinner />;
}
if (hasErrored) {
return <ErrorMessage />;
}
if (data) {
<h1>Hei {data.name} 👋</h1>;
}
}
Ganske enkelt: du skal vise en eller annen loading-state mens du henter data, eller en feilmelding dersom noe går galt i prosessen.
Koden over fungerer ikke, siden det mangler setIsLoading(false) i then ️og catch-blokkene. Da kan man havne i en tilstand der for eksempel både hasErrored = true og isLoading = true. Dermed vil vi alltid treffe den første if-blokka, og bare vise en spinner for alltid.
Problemet er altså at vi kan havne i “ugyldige” states der flere av feltene er satt samtidig, i stedet for at man man enten er i loading-state, error-state eller data state , som er det vi egentlig vil.
Dette problemet kan union-types hjelpe oss med, så vi i stedet skrive komponenter som ser slik ut 👇
const Komponent = () => {
const [state, setState] = useState<PersonResponse> ({type:'LOADING'});
useEffect(() => {
getPerson().then((res) => setState(res));
}, []);
switch (state.type) {
case 'LOADING':
return <Spinner />;
case 'ERROR':
return <ErrorMessage />;
case 'DATA':
<h1>Hei {state.person.name} 👋</h1>;
}
}
Det eneste PersonResponse gjør er å wrappe person-objektet ({name: string}) i en hjelpe-type som gjør det enklere å holde styr på hvilken state man er i.
Den har et felt som heter type, også kalt en “discriminant”, som enten har verdien ERROR, LOADING eller DATA.
interface PersonData {
type: 'DATA';
person: {
name: string;
};
}
interface PersonError {
type: 'ERROR';
}
interface PersonLoading {
type: 'LOADING';
}
type PersonResponse = PersonData | PersonError | PersonLoading;
Samme eksempel ligger på GitHub, med eksempel på hvordan du kan skrive API-kallet.
Der er i tillegg Reponse-wrapper-typen er skrevet med generics, slik at du kan gjenbruke samme type på flere API-kall.
Takk for at du leste! Husk å bruke Typescript på nye (og gamle) prosjekter fordi
🤠
Slik takla Kolonial.no korona-trykket
- Vi møtte ganske umiddelbart flaskehalser, forteller utviklerne.