Files
novi-lessons/Les04-TypeScript-Fundamentals/Les04-Huiswerk-ANTWOORDEN.md
2026-03-02 14:45:39 +01:00

18 KiB

Les 4: TypeScript Fundamentals - Huiswerk ANTWOORDEN

Deze antwoordsleutel toont de volledige conversie van JavaScript naar TypeScript voor het huiswerk van Les 4. Voor elk bestand wordt een interface, type definitions en de complete TypeScript-implementatie getoond.


1. users.ts - Gebruikersbeheer

Origineel JavaScript (users.js)

De originele JavaScript-versie bevat vijf functies voor gebruikersbeheer:

  • createUser(): Maakt een nieuwe gebruiker aan met unieke ID en timestamp
  • findUserByEmail(): Zoekt een gebruiker op emailadres
  • updateUser(): Past gebruikerseigenschappen aan
  • filterActiveUsers(): Filtert alleen actieve gebruikers
  • deactivateUser(): Deactiveert een gebruiker

Complete TypeScript Conversie

// src/users.ts

/**
 * User interface - Definieert de structuur van een gebruikersobject
 */
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  createdAt: Date;
}

/**
 * Maakt een nieuwe gebruiker aan
 * @param name - De naam van de gebruiker
 * @param email - Het emailadres van de gebruiker
 * @param age - De leeftijd van de gebruiker
 * @returns Een nieuw User-object
 */
function createUser(name: string, email: string, age: number): User {
  return {
    id: Math.random().toString(36).substr(2, 9),
    name,
    email,
    age,
    isActive: true,
    createdAt: new Date()
  };
}

/**
 * Zoekt een gebruiker op emailadres
 * @param users - Array van gebruikers
 * @param email - Het emailadres om naar te zoeken
 * @returns De gevonden User, of null als niet gevonden
 */
function findUserByEmail(users: User[], email: string): User | null {
  return users.find(user => user.email === email) || null;
}

/**
 * Werkt gebruikerseigenschappen bij
 * @param user - De originele gebruiker
 * @param updates - Gedeeltelijke updates (Partial<User>)
 * @returns De bijgewerkte User
 */
function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

/**
 * Filtert alleen actieve gebruikers
 * @param users - Array van gebruikers
 * @returns Array van actieve gebruikers
 */
function filterActiveUsers(users: User[]): User[] {
  return users.filter(user => user.isActive);
}

/**
 * Deactiveert een gebruiker
 * @param user - De gebruiker om te deactiveren
 * @returns De gebruiker met isActive = false
 */
function deactivateUser(user: User): User {
  return { ...user, isActive: false };
}

export { createUser, findUserByEmail, updateUser, filterActiveUsers, deactivateUser };
export type { User };

Sleutelbeslissingen bij TypeScript Conversie

  1. User Interface: Een interface is gedefinieerd die alle properties van een gebruiker typeert. Dit zorgt voor type-safety en autocomplete.

  2. Functie Parameters: Alle parameters zijn voorzien van expliciete TypeScript-typen:

    • name: string, email: string, age: number voor createUser()
    • users: User[] voor arrays van gebruikers
  3. Return Types: Alle functies hebben expliciete return types:

    • createUser() retourneert User
    • findUserByEmail() retourneert User | null (union type voor null-safety)
    • filterActiveUsers() en updateUser() retourneren User[] respectievelijk User
  4. Partial Type: De updateUser() functie accepteert Partial<User> omdat niet alle eigenschappen hoeven te worden bijgewerkt. Dit is een built-in TypeScript utility type.

  5. Export: Zowel functies als het User type worden geëxporteerd voor hergebruik in andere bestanden.


2. products.ts - Productbeheer

Origineel JavaScript (products.js)

De JavaScript-versie bevat:

  • CATEGORIES: Constante array met geldige categorieën
  • createProduct(): Maakt een nieuw product aan met validatie
  • applyDiscount(): Pas een kortingspercentage toe
  • getExpensiveProducts(): Filtert producten op minimale prijs
  • formatPrice(): Formatteert een prijs met valutasymbool
  • rateProduct(): Voegt een rating toe (1-5)

Complete TypeScript Conversie

// src/products.ts

/**
 * Union type voor productcategorieën
 */
type Category = 'electronics' | 'clothing' | 'food' | 'books' | 'sports';

/**
 * Product interface - Definieert de structuur van een productobject
 */
interface Product {
  id: string;
  name: string;
  price: number;
  category: Category;
  description: string | null;
  inStock: boolean;
  rating: number | null;
}

/**
 * Geldige productcategorieën
 */
const CATEGORIES: Category[] = ['electronics', 'clothing', 'food', 'books', 'sports'];

/**
 * Maakt een nieuw product aan
 * @param name - Productnaam
 * @param price - Productprijs
 * @param category - Productcategorie (moet in CATEGORIES zitten)
 * @param description - Optionele productbeschrijving
 * @returns Een nieuw Product-object
 * @throws Error als categorie ongeldig is
 */
function createProduct(
  name: string,
  price: number,
  category: Category,
  description?: string
): Product {
  if (!CATEGORIES.includes(category)) {
    throw new Error(`Invalid category: ${category}`);
  }
  return {
    id: Math.random().toString(36).substr(2, 9),
    name,
    price,
    category,
    description: description || null,
    inStock: true,
    rating: null
  };
}

/**
 * Passe een kortingspercentage op een product toe
 * @param product - Het originele product
 * @param percentage - Kortingspercentage (0-100)
 * @returns Product met verlaagde prijs
 * @throws Error als percentage buiten bereik is
 */
function applyDiscount(product: Product, percentage: number): Product {
  if (percentage < 0 || percentage > 100) {
    throw new Error('Discount must be between 0 and 100');
  }
  const discountedPrice = product.price * (1 - percentage / 100);
  return { ...product, price: Math.round(discountedPrice * 100) / 100 };
}

/**
 * Filtert producten op minimale prijs
 * @param products - Array van producten
 * @param minPrice - Minimale prijs
 * @returns Array van producten met prijs >= minPrice
 */
function getExpensiveProducts(products: Product[], minPrice: number): Product[] {
  return products.filter(product => product.price >= minPrice);
}

/**
 * Formatteert een prijs met valutasymbool
 * @param price - Het prijsbedrag
 * @param currency - Valutacode ('EUR', 'USD', 'GBP')
 * @returns Geformateerde prijsstring (bijv. '€19.99')
 */
function formatPrice(price: number, currency: string): string {
  const currencySymbols: Record<string, string> = {
    EUR: '€',
    USD: '$',
    GBP: '£'
  };
  const symbol = currencySymbols[currency] || currency;
  return `${symbol}${price.toFixed(2)}`;
}

/**
 * Voegt een rating toe aan een product
 * @param product - Het originele product
 * @param rating - Rating waarde (1-5)
 * @returns Product met rating
 * @throws Error als rating buiten bereik is
 */
function rateProduct(product: Product, rating: number): Product {
  if (rating < 1 || rating > 5) {
    throw new Error('Rating must be between 1 and 5');
  }
  return { ...product, rating };
}

export { createProduct, applyDiscount, getExpensiveProducts, formatPrice, rateProduct, CATEGORIES };
export type { Product, Category };

Sleutelbeslissingen bij TypeScript Conversie

  1. Category Union Type: In plaats van een willekeurige string, gebruiken we een union type type Category = 'electronics' | 'clothing' | 'food' | 'books' | 'sports'. Dit voorkomt ongeldige categorieën en geeft betere autocomplete in IDE's.

  2. Product Interface: Definieert alle eigenschappen met correcte typen:

    • category: Category in plaats van string (type-safe)
    • description: string | null en rating: number | null voor optionele waarden
    • price: number voor numerieke waarden
  3. CATEGORIES Type: const CATEGORIES: Category[] zorgt ervoor dat de array alleen geldige categorieën bevat.

  4. Overloading Voorkomen: De createProduct() functie accepteert description?: string (optionele parameter) in plaats van description = null. Dit is schoner en meer TypeScript-idiomatic.

  5. Record<string, string>: Voor currencySymbols gebruiken we Record<string, string> in plaats van een gewone object, wat typezekerheid biedt.

  6. Validatie Types: De functies valideren hun inputs en gooien TypeErrors met duidelijke berichten.


3. orders.ts - Orderbeheer

Origineel JavaScript (orders.js)

De JavaScript-versie bevat:

  • createOrder(): Maakt een nieuwe order aan
  • calculateTotal(): Berekent de totale prijs
  • updateOrderStatus(): Wijzigt orderstatus met validatie
  • getOrdersByUser(): Filtert orders per gebruiker
  • getOrdersByStatus(): Filtert orders per status

Complete TypeScript Conversie

// src/orders.ts

/**
 * Union type voor mogelijke orderstatussen
 */
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';

/**
 * OrderItem interface - Representeert een product in een order
 */
interface OrderItem {
  productId: string;
  name: string;
  price: number;
}

/**
 * Order interface - Definieert de structuur van een orderobject
 */
interface Order {
  id: string;
  userId: string;
  products: OrderItem[];
  total: number;
  status: OrderStatus;
  createdAt: Date;
}

/**
 * Type voor geldige statustransities
 */
type ValidTransitions = Record<OrderStatus, OrderStatus[]>;

/**
 * Maakt een nieuwe order aan
 * @param user - Het User-object (moet minimaal id hebben)
 * @param products - Array van Product-objecten
 * @returns Een nieuw Order-object met status 'pending'
 */
function createOrder(
  user: { id: string },
  products: Array<{ id: string; name: string; price: number }>
): Order {
  return {
    id: Math.random().toString(36).substr(2, 9),
    userId: user.id,
    products: products.map(p => ({ productId: p.id, name: p.name, price: p.price })),
    total: calculateTotal(products),
    status: 'pending',
    createdAt: new Date()
  };
}

/**
 * Berekent het totaalbedrag van producten
 * @param products - Array van producten met price property
 * @returns Totaalbedrag
 */
function calculateTotal(products: Array<{ price: number }>): number {
  return products.reduce((sum, product) => sum + product.price, 0);
}

/**
 * Wijzigt de status van een order
 * @param order - De originele order
 * @param newStatus - De nieuwe status
 * @returns Order met bijgewerkte status
 * @throws Error als transactie ongeldig is
 */
function updateOrderStatus(order: Order, newStatus: OrderStatus): Order {
  const validTransitions: ValidTransitions = {
    pending: ['processing', 'cancelled'],
    processing: ['shipped', 'cancelled'],
    shipped: ['delivered'],
    delivered: [],
    cancelled: []
  };

  if (!validTransitions[order.status].includes(newStatus)) {
    throw new Error(`Cannot transition from ${order.status} to ${newStatus}`);
  }

  return { ...order, status: newStatus };
}

/**
 * Filtert orders van een specifieke gebruiker
 * @param orders - Array van orders
 * @param userId - De gebruikers-ID
 * @returns Array van orders van die gebruiker
 */
function getOrdersByUser(orders: Order[], userId: string): Order[] {
  return orders.filter(order => order.userId === userId);
}

/**
 * Filtert orders op status
 * @param orders - Array van orders
 * @param status - De filterstatus
 * @returns Array van orders met die status
 */
function getOrdersByStatus(orders: Order[], status: OrderStatus): Order[] {
  return orders.filter(order => order.status === status);
}

export { createOrder, calculateTotal, updateOrderStatus, getOrdersByUser, getOrdersByStatus };
export type { Order, OrderStatus, OrderItem };

Sleutelbeslissingen bij TypeScript Conversie

  1. OrderStatus Union Type: type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled' zorgt ervoor dat alleen geldige statussen kunnen worden gebruikt.

  2. OrderItem Interface: Gescheiden interface voor orderitems maakt de code modulair en leesbaar.

  3. Order Interface: Gebruikt het OrderStatus type voor de status property in plaats van string.

  4. Flexible Parameter Types: createOrder() accepteert user: { id: string } in plaats van volledige User-interface. Dit maakt de functie flexibel en ontkoppeld van usersbeheer.

  5. ValidTransitions Type: Record<OrderStatus, OrderStatus[]> type typeert de transities-object correct. Elke status mappen naar een array van geldige volgende statussen.

  6. Generieke Product Parameter: calculateTotal() accepteert Array<{ price: number }> wat betekent dat het werkt met elk object dat een price-property heeft.

  7. Status Filtering: getOrdersByStatus() accepteert status: OrderStatus waardoor alleen geldige statussen kunnen worden gefilterd.


4. utils.ts - Hulpfuncties

Origineel JavaScript (utils.js)

De JavaScript-versie bevat:

  • formatDate(): Formatteert een Date naar DD-MM-YYYY
  • generateId(): Genereert een willekeurige ID-string
  • validateEmail(): Valideert e-mailadressen met regex
  • sortBy(): Generieke functie om items te sorteren
  • groupBy(): Generieke functie om items te groeperen

Complete TypeScript Conversie

// src/utils.ts

/**
 * Formatteert een Date-object naar DD-MM-YYYY string
 * @param date - De Date-object
 * @returns Geformateerde datestring
 */
function formatDate(date: Date): string {
  const day = String(date.getDate()).padStart(2, '0');
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const year = date.getFullYear();
  return `${day}-${month}-${year}`;
}

/**
 * Genereert een willekeurige ID-string
 * @returns Unieke ID-string
 */
function generateId(): string {
  return Math.random().toString(36).substr(2, 9);
}

/**
 * Valideert of een string een geldig e-mailadres is
 * @param email - De email-string om te valideren
 * @returns true als geldig, false als ongeldig
 */
function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

/**
 * Sorteert een array van items op een specifieke property
 * Generieke functie met type parameters
 * @param items - Array van items om te sorteren
 * @param key - De property-naam om op te sorteren
 * @param direction - Sorteerrichting 'asc' of 'desc'
 * @returns Gesorteerde array (original wordt niet gewijzigd)
 */
function sortBy<T extends Record<string, any>>(
  items: T[],
  key: keyof T,
  direction: 'asc' | 'desc'
): T[] {
  return [...items].sort((a, b) => {
    const aValue = a[key];
    const bValue = b[key];

    if (direction === 'desc') {
      return aValue > bValue ? -1 : 1;
    }
    return aValue > bValue ? 1 : -1;
  });
}

/**
 * Groepeert items in een object op basis van een property
 * Generieke functie met type parameters
 * @param items - Array van items om te groeperen
 * @param key - De property-naam om op te groeperen
 * @returns Object waar keys de groepwaarden zijn en values arrays van items
 */
function groupBy<T extends Record<string, any>>(
  items: T[],
  key: keyof T
): Record<string | number | symbol, T[]> {
  return items.reduce((groups, item) => {
    const value = item[key];
    if (!groups[value]) {
      groups[value] = [];
    }
    groups[value].push(item);
    return groups;
  }, {} as Record<string | number | symbol, T[]>);
}

export { formatDate, generateId, validateEmail, sortBy, groupBy };

Sleutelbeslissingen bij TypeScript Conversie

  1. Generieke Type Parameters:

    • <T extends Record<string, any>> zorgt ervoor dat T elk object-type kan zijn met string-keys
    • Dit maakt sortBy() en groupBy() herbruikbaar voor alle object-arrays
  2. keyof T: In plaats van een string voor de key parameter, gebruiken we keyof T. Dit ziet er in de IDE voor dat je alleen geldige properties van T kunt selecteren.

  3. Literal Union Type voor Direction: direction: 'asc' | 'desc' in plaats van string, wat fouten voorkomt met ongeldige richtingen.

  4. Return Types:

    • sortBy() retourneert T[] (dezelfde type als input)
    • groupBy() retourneert Record<string | number | symbol, T[]>
  5. Spread Operator Preserving: In sortBy() gebruiken we [...items] om een kopie te maken. TypeScript begrijpt dat dit een T[] retourneert.

  6. Type Casting: In groupBy() gebruiken we as Record<string | number | symbol, T[]> om TypeScript te helpen begrijpen dat we een lege Record initialiseren.

  7. Non-Mutating Approach: Beide functies wijzigen de originele array niet, wat een best practice is voor pure functions.


Samenvattend: TypeScript Conversie Principes

1. Interfaces voor Data Structures

  • Elk belangrijk data-object krijgt een interface (User, Product, Order)
  • Interfaces documenteren de verwachte structuur en verbeteren IDE support

2. Union Types voor Enumeraties

  • In plaats van willekeurige strings, gebruiken we union types:
    • type Category = 'electronics' | 'clothing' | ...
    • type OrderStatus = 'pending' | 'processing' | ...
  • Dit voorkomt fouten en verbetert type-safety

3. Generieke Types

  • Voor herbruikbare functies als sortBy() en groupBy() gebruiken we generieke parameters
  • <T extends Record<string, any>> zorgt voor type-safety terwijl flexibiliteit behouden blijft

4. Null/Undefined Handling

  • Union types met null/undefined: string | null, number | null
  • Dit dwingt expliciete handling van lege waarden

5. Parameter Type Hints

  • Alle parameters hebben expliciete TypeScript-typen
  • Optionele parameters gebruiken ? of ...?: Type syntax

6. Return Type Annotations

  • Alle functies hebben expliciete return type annotations
  • Dit helpt compile-time fouten opsporen en documenteert intent

7. keyof en Generieke Constraints

  • keyof T zorgt ervoor dat je alleen geldige properties kunt selecteren
  • extends Record<string, any> constrained het type naar objecten

8. Typing van Object Methods

  • Partial<T> voor optionele updates
  • Record<K, V> voor type-safe object literals
  • Pick<T, Keys> en Omit<T, Keys> voor subset typen

Test Compatibiliteit

Alle bovenstaande TypeScript conversies zijn ontworpen om perfect te werken met de bestaande test suite:

  • users.test.ts: Tests gebruiken User interface
  • products.test.ts: Tests gebruiken Product interface en Category type
  • orders.test.ts: Tests gebruiken Order, OrderStatus en gerelateerde typen
  • utils.test.ts: Tests gebruiken generieke functies met impliciete type-inferentie

De TypeScript-versies behouden exact dezelfde logic en runtime-gedrag als de originele JavaScript-versies, terwijl ze de voordelen van type-safety bieden.