Best practices in TypeScript-programmeren

Best practices in TypeScript-programmeren

Dit artikel legt de best practices uit in TypeScript-programmeren.

Deze gids legt praktische best practices uit om de types van TypeScript te benutten om bugs te verminderen en leesbaardere code te schrijven.

YouTube Video

Best practices in TypeScript-programmeren

Het grootste voordeel van TypeScript is 'bugs voorkomen met types en de bedoeling van de code duidelijk maken'.

Best practices zijn niet slechts regels, maar een reeks principes om veilige, leesbare en onderhoudbare code te schrijven. Hieronder introduceren we veelgebruikte TypeScript best practices met praktische voorbeelden.

Vermijd any en geef altijd zinvolle definities aan types

Laten we eerst kijken naar het punt van 'het vermijden van any en het geven van betekenisvolle types.'.

any schakelt typecontrole volledig uit, waardoor het doel van het gebruik van TypeScript wordt tenietgedaan. In plaats van any te gebruiken om dingen te laten werken, is het belangrijk om types te bieden die zo beschrijvend mogelijk zijn.

1// Bad
2function parse(data: any) {
3  return data.value;
4}

Deze code kan elke waarde accepteren, waardoor runtime fouten niet kunnen worden voorkomen.

1// Good
2type ParsedData = {
3  value: string;
4};
5
6function parse(data: ParsedData): string {
7  return data.value;
8}

Door types te definiëren, verduidelijk je de bedoeling van invoer en uitvoer en verbeter je de veiligheid.

Definieer objectstructuren altijd expliciet met type of interface.

Laten we vervolgens kijken naar het punt van 'altijd objectstructuren expliciet definiëren met behulp van type of interface.'.

Als je objecten ad-hoc gebruikt, kan hun structuur onduidelijk worden. Haal types altijd eruit voor herbruikbaarheid en onderhoudbaarheid.

1// Bad
2function createUser(user: { name: string; age: number }) {
3  console.log(user.name);
4}

Zelfs in kleine stukjes code is het belangrijk om de gewoonte te ontwikkelen om typen te scheiden.

1// Good
2type User = {
3  name: string;
4  age: number;
5};
6
7function createUser(user: User): void {
8  console.log(user.name);
9}

Door types te benoemen wordt het begrijpen van de gehele codebase veel makkelijker.

Gebruik unietypes om alle mogelijke toestanden nauwkeurig weer te geven.

Als je ruwe string of number types gebruikt voor conditionals, kunnen onverwachte waarden erdoor glippen. Door unietypen te gebruiken, kun je alleen de toegestane toestanden op typeniveau weergeven.

1// Bad
2function setStatus(status: string) {
3  console.log(status);
4}

In deze code kunnen zinloze strings of onjuiste waarden niet tijdens het compileren worden gedetecteerd.

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5  console.log(status);
6}

Door unietypen te gebruiken, kun je 'onmogelijke toestanden' betrouwbaar uitsluiten tijdens het compileren. Hierdoor verbeteren de veiligheid van voorwaardelijke vertakkingen en de betrouwbaarheid van de code.

Behandel null en undefined expliciet.

Laten we vervolgens kijken naar het punt van 'het expliciet omgaan met null en undefined.'.

In TypeScript is het belangrijk om het mogelijke ontbreken van een waarde in het type uit te drukken. Als je dingen vaag laat, kunnen ze tot runtime fouten leiden.

1type User = {
2  name: string;
3  email?: string;
4};

email kan ontbreken, dus je moet daar rekening mee houden.

1function printEmail(user: User): void {
2  if (user.email) {
3    console.log(user.email);
4  }
5}

Controleer optionele waarden altijd voordat je ze gebruikt.

Gebruik type-asserties (as) alleen als laatste redmiddel.

Laten we vervolgens kijken naar het punt van 'het niet overmatig gebruiken van type-asserties.'.

Type-asserties omzeilen tijdelijk de typecontrole van TypeScript om te verklaren: 'Ik weet dat deze waarde van dit type is.'. Overmatig gebruik ervan ondermijnt de typesafety.

1// Bad
2const value = input as string;

In deze code treedt er geen fout op, zelfs als de werkelijke waarde geen string is, wat kan leiden tot runtimefouten. Zoals in de volgende code wordt getoond, kies ervoor om eerst veilig te controleren met type guards.

1// Good
2function isString(value: unknown): value is string {
3  return typeof value === "string";
4}
5
6if (isString(input)) {
7  console.log(input.toUpperCase());
8}

Type guards zijn een mechanisme om tijdens het controleren van de werkelijke waarde op een veilige manier het type te bepalen. Door type guards prioriteit te geven boven type-asserties, kun je runtimefouten gemakkelijker voorkomen.

Vertrouw niet te veel op type-inferentie voor returntypes.

Laten we vervolgens kijken naar het punt van 'niet te veel vertrouwen op inferentie voor retourtypes.'.

TypeScript's type-inferentie is krachtig, maar het is veiliger om de returntypes van publieke functies expliciet te annoteren. Dit minimaliseert de mogelijke impact van toekomstige wijzigingen.

1// Bad
2function sum(a: number, b: number) {
3  return a + b;
4}

Geef je bedoeling duidelijk aan, zelfs in kleine functies.

1// Good
2function sum(a: number, b: number): number {
3  return a + b;
4}

Het uitschrijven van returntypes verhoogt de stabiliteit van je API.

Behandel invoer veilig door gebruik te maken van unknown.

Laten we vervolgens kijken naar het punt van 'het veilig accepteren van externe invoer met behulp van unknown.'.

Gebruik voor externe invoer zoals APIs, JSON of gebruikersinvoer unknown in plaats van any. Op deze manier zorg je ervoor dat alle waarden gevalideerd worden, waardoor typeveiligheid behouden blijft.

1// Bad
2function handleResponse(data: any) {
3  console.log(data.id);
4}

Zo valideer je types met unknown.

 1// Good
 2function handleResponse(data: unknown): void {
 3  if (
 4    typeof data === "object" &&
 5    data !== null &&
 6    "id" in data
 7  ) {
 8    console.log((data as { id: number }).id);
 9  }
10}

unknown kan niet direct gebruikt worden; het is een type dat validatie vereist. Het is vooral effectief bij het verwerken van externe invoer.

Verhoog de expressiviteit door kleine types te combineren.

Laten we vervolgens kijken naar het punt van 'de expressiviteit vergroten door kleine types te combineren.'.

Grote types in één keer definiëren vermindert de leesbaarheid en onderhoudbaarheid. Verdeel je types in betekenisvolle eenheden en combineer ze waar nodig.

 1type Id = number;
 2
 3type UserProfile = {
 4  id: Id;
 5  name: string;
 6};
 7
 8type UserWithStatus = UserProfile & {
 9  status: "active" | "inactive";
10};

Types als componenten behandelen helpt je ontwerp te organiseren.

type en interface

Voordelen van interface

type en interface kunnen beide types definiëren, maar hun beoogde gebruik en eigenschappen verschillen. Door ze voor de juiste doeleinden te gebruiken, wordt de bedoeling van je typedefinities duidelijker.

 1// Bad
 2type User = {
 3  id: number;
 4  name: string;
 5};
 6
 7type AdminUser = {
 8  id: number;
 9  name: string;
10  role: "admin";
11};

Als je gemeenschappelijke delen zo dupliceert, wordt je code kwetsbaar voor veranderingen.

1// Good
2interface User {
3  id: number;
4  name: string;
5}
6
7interface AdminUser extends User {
8  role: "admin";
9}

interface is ideaal voor ontwerpen die extensie (extends) vereisen en is het meest geschikt om de 'vorm' van objecten uit te drukken.

Voordelen van type

Aan de andere kant is type expressiever en beter geschikt voor het omgaan met unie- en intersectietypes.

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5  | { status: "success"; data: T }
6  | { status: "error"; message: string };

type is zeer geschikt om toestanden, opties en combinaties uit te drukken.

Richtlijnen voor de keuze tussen type en interface

Als vuistregel: gebruik interface voor objectstructuren en contracten, en type als je de expressiviteit van unie-, intersectie- of typeoperaties nodig hebt.

Beide werken vergelijkbaar, maar het is belangrijk om te kiezen op basis van wat het doel van het type het beste communiceert.

Zie je types als documentatie.

Laten we tot slot kijken naar het punt van 'types als documentatie schrijven.'.

Goede typedefinities geven meer informatie dan commentaar ooit kan. Het is belangrijk te streven naar een situatie waarin 'de specificatie te begrijpen is door alleen naar de types te kijken.'.

1type ApiError = {
2  code: number;
3  message: string;
4  retryable: boolean;
5};

Op deze manier is een van de grote sterktes van TypeScript dat typedefinities kunnen functioneren als een soort specificatiedocument.

Samenvatting

TypeScript best practices gaan niet om buitensporige strengheid. De essentie is om de bedoeling te verduidelijken met types en code te schrijven die robuust is voor verandering.

Door kleine regels op te bouwen in je dagelijkse ontwikkeling kun je op de lange termijn voordelen behalen zoals 'makkelijkere code reviews', 'minder bugs' en 'beter begrip voor jezelf in de toekomst en voor anderen.'.

Als eerste, door te denken 'hoe kan ik dit met types uitdrukken?', schrijf je code van hoge kwaliteit in echte TypeScript-stijl.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video