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 timestampfindUserByEmail(): Zoekt een gebruiker op emailadresupdateUser(): Past gebruikerseigenschappen aanfilterActiveUsers(): Filtert alleen actieve gebruikersdeactivateUser(): 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
-
User Interface: Een interface is gedefinieerd die alle properties van een gebruiker typeert. Dit zorgt voor type-safety en autocomplete.
-
Functie Parameters: Alle parameters zijn voorzien van expliciete TypeScript-typen:
name: string,email: string,age: numbervoorcreateUser()users: User[]voor arrays van gebruikers
-
Return Types: Alle functies hebben expliciete return types:
createUser()retourneertUserfindUserByEmail()retourneertUser | null(union type voor null-safety)filterActiveUsers()enupdateUser()retournerenUser[]respectievelijkUser
-
Partial Type: De
updateUser()functie accepteertPartial<User>omdat niet alle eigenschappen hoeven te worden bijgewerkt. Dit is een built-in TypeScript utility type. -
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ëncreateProduct(): Maakt een nieuw product aan met validatieapplyDiscount(): Pas een kortingspercentage toegetExpensiveProducts(): Filtert producten op minimale prijsformatPrice(): Formatteert een prijs met valutasymboolrateProduct(): 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
-
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. -
Product Interface: Definieert alle eigenschappen met correcte typen:
category: Categoryin plaats vanstring(type-safe)description: string | nullenrating: number | nullvoor optionele waardenprice: numbervoor numerieke waarden
-
CATEGORIES Type:
const CATEGORIES: Category[]zorgt ervoor dat de array alleen geldige categorieën bevat. -
Overloading Voorkomen: De
createProduct()functie accepteertdescription?: string(optionele parameter) in plaats vandescription = null. Dit is schoner en meer TypeScript-idiomatic. -
Record<string, string>: Voor
currencySymbolsgebruiken weRecord<string, string>in plaats van een gewone object, wat typezekerheid biedt. -
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 aancalculateTotal(): Berekent de totale prijsupdateOrderStatus(): Wijzigt orderstatus met validatiegetOrdersByUser(): Filtert orders per gebruikergetOrdersByStatus(): 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
-
OrderStatus Union Type:
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'zorgt ervoor dat alleen geldige statussen kunnen worden gebruikt. -
OrderItem Interface: Gescheiden interface voor orderitems maakt de code modulair en leesbaar.
-
Order Interface: Gebruikt het
OrderStatustype voor de status property in plaats vanstring. -
Flexible Parameter Types:
createOrder()accepteertuser: { id: string }in plaats van volledige User-interface. Dit maakt de functie flexibel en ontkoppeld van usersbeheer. -
ValidTransitions Type:
Record<OrderStatus, OrderStatus[]>type typeert de transities-object correct. Elke status mappen naar een array van geldige volgende statussen. -
Generieke Product Parameter:
calculateTotal()accepteertArray<{ price: number }>wat betekent dat het werkt met elk object dat een price-property heeft. -
Status Filtering:
getOrdersByStatus()accepteertstatus: OrderStatuswaardoor 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-YYYYgenerateId(): Genereert een willekeurige ID-stringvalidateEmail(): Valideert e-mailadressen met regexsortBy(): Generieke functie om items te sorterengroupBy(): 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
-
Generieke Type Parameters:
<T extends Record<string, any>>zorgt ervoor dat T elk object-type kan zijn met string-keys- Dit maakt
sortBy()engroupBy()herbruikbaar voor alle object-arrays
-
keyof T: In plaats van een string voor de
keyparameter, gebruiken wekeyof T. Dit ziet er in de IDE voor dat je alleen geldige properties van T kunt selecteren. -
Literal Union Type voor Direction:
direction: 'asc' | 'desc'in plaats vanstring, wat fouten voorkomt met ongeldige richtingen. -
Return Types:
sortBy()retourneertT[](dezelfde type als input)groupBy()retourneertRecord<string | number | symbol, T[]>
-
Spread Operator Preserving: In
sortBy()gebruiken we[...items]om een kopie te maken. TypeScript begrijpt dat dit eenT[]retourneert. -
Type Casting: In
groupBy()gebruiken weas Record<string | number | symbol, T[]>om TypeScript te helpen begrijpen dat we een lege Record initialiseren. -
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()engroupBy()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...?: Typesyntax
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 Tzorgt ervoor dat je alleen geldige properties kunt selecterenextends Record<string, any>constrained het type naar objecten
8. Typing van Object Methods
Partial<T>voor optionele updatesRecord<K, V>voor type-safe object literalsPick<T, Keys>enOmit<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
Userinterface - products.test.ts: Tests gebruiken
Productinterface enCategorytype - orders.test.ts: Tests gebruiken
Order,OrderStatusen 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.