fix: update les 4
This commit is contained in:
@@ -1,393 +1,234 @@
|
||||
# Les 5: TypeScript Basics
|
||||
# Les 5: TypeScript voor React
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 5-9)
|
||||
**Deel 2: Technical Foundations** (Les 4-9)
|
||||
|
||||
## Beschrijving
|
||||
Introductie tot TypeScript voor React developers. Leer waarom TypeScript waardevol is, hoe je types schrijft, en hoe je het combineert met React.
|
||||
Verdieping in TypeScript met focus op React-patronen. Studenten leren generics, utility types, en hoe je React components, hooks, events en API calls correct typt. Voorbereiding op Les 6 waar ze met Next.js aan de slag gaan.
|
||||
|
||||
**Voorkennis:** Les 4 (TypeScript Fundamentals) — basic types, interfaces, union types, type aliases, functies typen.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
|
||||
### Waarom TypeScript?
|
||||
|
||||
**Het probleem met JavaScript:**
|
||||
```javascript
|
||||
function greet(name) {
|
||||
return "Hello, " + name.toUpperCase();
|
||||
}
|
||||
|
||||
greet(42); // Runtime error! 42.toUpperCase is not a function
|
||||
```
|
||||
|
||||
**De oplossing met TypeScript:**
|
||||
```typescript
|
||||
function greet(name: string): string {
|
||||
return "Hello, " + name.toUpperCase();
|
||||
}
|
||||
|
||||
greet(42); // Compile error! Argument of type 'number' is not assignable to type 'string'
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- Fouten vinden VOORDAT je code runt
|
||||
- Betere autocomplete in je editor
|
||||
- Code is zelf-documenterend
|
||||
- AI tools begrijpen je code beter
|
||||
|
||||
---
|
||||
|
||||
### Basic Types
|
||||
### Generics (20 min)
|
||||
- Waarom generics? Herbruikbare, type-safe code
|
||||
- `Array<T>`, `Promise<T>` — generics die ze al kennen
|
||||
- Eigen generics schrijven: `function getFirst<T>(items: T[]): T`
|
||||
- Generics met constraints: `<T extends { id: string }>`
|
||||
- `keyof` operator: `function getValue<T, K extends keyof T>(obj: T, key: K): T[K]`
|
||||
|
||||
```typescript
|
||||
// Primitives
|
||||
let name: string = "Tim";
|
||||
let age: number = 25;
|
||||
let isStudent: boolean = true;
|
||||
// Generic functie
|
||||
function wrapInArray<T>(value: T): T[] {
|
||||
return [value];
|
||||
}
|
||||
|
||||
// Arrays
|
||||
let numbers: number[] = [1, 2, 3];
|
||||
let names: string[] = ["Tim", "Anna"];
|
||||
wrapInArray("hello"); // string[]
|
||||
wrapInArray(42); // number[]
|
||||
|
||||
// Alternative array syntax
|
||||
let scores: Array<number> = [90, 85, 88];
|
||||
|
||||
// Objects
|
||||
let user: { name: string; age: number } = {
|
||||
name: "Tim",
|
||||
age: 25
|
||||
};
|
||||
// Met constraint
|
||||
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||||
return obj[key];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Type Inference
|
||||
|
||||
TypeScript raadt types vaak zelf:
|
||||
|
||||
```typescript
|
||||
// TypeScript weet dat dit een string is
|
||||
let message = "Hello"; // type: string
|
||||
|
||||
// TypeScript weet dat dit een number is
|
||||
let count = 42; // type: number
|
||||
|
||||
// TypeScript weet wat de functie returned
|
||||
function double(x: number) {
|
||||
return x * 2; // return type: number (inferred)
|
||||
}
|
||||
```
|
||||
|
||||
**Regel:** Je hoeft niet altijd types te schrijven. Laat TypeScript inferren waar mogelijk.
|
||||
|
||||
---
|
||||
|
||||
### Interfaces
|
||||
|
||||
Voor het beschrijven van object shapes:
|
||||
### Utility Types (15 min)
|
||||
- `Partial<T>` — alle properties optioneel (handig voor updates)
|
||||
- `Pick<T, K>` — selecteer specifieke properties
|
||||
- `Omit<T, K>` — alles behalve specifieke properties
|
||||
- `Record<K, V>` — key-value mapping
|
||||
- Praktisch voorbeeld: `updateUser(id: string, data: Partial<User>)`
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
isActive: boolean;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
id: 1,
|
||||
name: "Tim",
|
||||
email: "tim@example.com",
|
||||
isActive: true
|
||||
};
|
||||
// Partial: voor update functies
|
||||
function updateUser(id: string, updates: Partial<User>): User { ... }
|
||||
updateUser("1", { name: "Tim" }); // alleen name updaten
|
||||
|
||||
// Optional properties met ?
|
||||
interface Product {
|
||||
id: number;
|
||||
name: string;
|
||||
price: number;
|
||||
description?: string; // optioneel
|
||||
}
|
||||
// Omit: voor create functies (id wordt server-side gegenereerd)
|
||||
type CreateUserInput = Omit<User, "id">;
|
||||
|
||||
// Pick: voor specifieke views
|
||||
type UserPreview = Pick<User, "id" | "name">;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Type Aliases
|
||||
|
||||
Alternatief voor interfaces, meer flexibel:
|
||||
### React Props Typen (20 min)
|
||||
- Interface voor component props
|
||||
- Children typen met `React.ReactNode`
|
||||
- Callback props: `onClick: () => void`, `onChange: (value: string) => void`
|
||||
- Spread props en prop forwarding
|
||||
- Default values met destructuring
|
||||
|
||||
```typescript
|
||||
// Type alias voor object
|
||||
type User = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
// Type alias voor union types
|
||||
type Status = "pending" | "approved" | "rejected";
|
||||
|
||||
// Type alias voor functie
|
||||
type GreetFunction = (name: string) => string;
|
||||
```
|
||||
|
||||
**Interface vs Type:**
|
||||
- Interface: voor objecten, kan extended worden
|
||||
- Type: voor alles, meer flexibel
|
||||
|
||||
---
|
||||
|
||||
### TypeScript met React
|
||||
|
||||
**Props typen:**
|
||||
|
||||
```typescript
|
||||
// Interface voor props
|
||||
interface ButtonProps {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
interface CardProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "highlighted";
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
// Component met typed props
|
||||
function Button({ label, onClick, disabled = false }: ButtonProps) {
|
||||
function Card({ title, children, variant = "default", onClose }: CardProps) {
|
||||
return (
|
||||
<button onClick={onClick} disabled={disabled}>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Gebruik
|
||||
<Button label="Click me" onClick={() => console.log("Clicked!")} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### useState met Types
|
||||
|
||||
```typescript
|
||||
import { useState } from 'react';
|
||||
|
||||
// Type inference werkt vaak
|
||||
const [count, setCount] = useState(0); // number
|
||||
|
||||
// Explicit type voor complexe data
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
// Array van objecten
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Generics Basics
|
||||
|
||||
Generics maken code herbruikbaar:
|
||||
|
||||
```typescript
|
||||
// Array is een generic type
|
||||
const numbers: Array<number> = [1, 2, 3];
|
||||
const names: Array<string> = ["Tim", "Anna"];
|
||||
|
||||
// Promise is een generic type
|
||||
async function fetchUser(): Promise<User> {
|
||||
const response = await fetch('/api/user');
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Je kunt ook eigen generics maken
|
||||
function firstElement<T>(arr: T[]): T | undefined {
|
||||
return arr[0];
|
||||
}
|
||||
|
||||
const first = firstElement([1, 2, 3]); // type: number | undefined
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Veelvoorkomende Errors
|
||||
|
||||
**Error 1: Type 'X' is not assignable to type 'Y'**
|
||||
```typescript
|
||||
let name: string = 42; // Error!
|
||||
// Fix: gebruik correct type
|
||||
let name: string = "Tim";
|
||||
```
|
||||
|
||||
**Error 2: Property 'X' does not exist on type 'Y'**
|
||||
```typescript
|
||||
interface User { name: string; }
|
||||
const user: User = { name: "Tim" };
|
||||
console.log(user.age); // Error! 'age' bestaat niet
|
||||
// Fix: voeg property toe aan interface
|
||||
```
|
||||
|
||||
**Error 3: Object is possibly 'undefined'**
|
||||
```typescript
|
||||
const users: User[] = [];
|
||||
console.log(users[0].name); // Error! users[0] kan undefined zijn
|
||||
// Fix: check eerst
|
||||
if (users[0]) {
|
||||
console.log(users[0].name);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### JS naar TS Omzetten
|
||||
|
||||
**Stap 1:** Rename `.js` naar `.tsx` (voor React) of `.ts`
|
||||
|
||||
**Stap 2:** Fix de rode errors - meestal:
|
||||
- Voeg types toe aan function parameters
|
||||
- Maak interfaces voor objecten
|
||||
- Handle nullable values
|
||||
|
||||
**Voorbeeld:**
|
||||
|
||||
```javascript
|
||||
// Voorheen (JavaScript)
|
||||
function UserCard({ user }) {
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Nu (TypeScript)
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserCardProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
function UserCard({ user }: UserCardProps) {
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- OpenCode/WebStorm
|
||||
- TypeScript (via Next.js)
|
||||
- React
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
|
||||
### TypeScript Hands-on
|
||||
|
||||
**Deel 1: JS naar TS Omzetten (45 min)**
|
||||
|
||||
Gegeven JavaScript component:
|
||||
|
||||
```javascript
|
||||
function ProductCard({ product, onAddToCart }) {
|
||||
return (
|
||||
<div className="p-4 border rounded">
|
||||
<h2>{product.name}</h2>
|
||||
<p>${product.price}</p>
|
||||
{product.description && <p>{product.description}</p>}
|
||||
<button onClick={() => onAddToCart(product.id)}>
|
||||
Add to Cart
|
||||
</button>
|
||||
<div className={`card card-${variant}`}>
|
||||
<h2>{title}</h2>
|
||||
{onClose && <button onClick={onClose}>×</button>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Zet dit om naar TypeScript:
|
||||
1. Maak `Product` interface
|
||||
2. Maak `ProductCardProps` interface
|
||||
3. Type de component
|
||||
4. Fix alle TypeScript errors
|
||||
---
|
||||
|
||||
**Deel 2: Interfaces Schrijven (30 min)**
|
||||
### useState & useEffect Typen (15 min)
|
||||
- Type inference bij useState: `useState(0)` → number
|
||||
- Explicit types: `useState<User | null>(null)`
|
||||
- Arrays: `useState<Product[]>([])`
|
||||
- useEffect met async patterns
|
||||
|
||||
Maak interfaces voor:
|
||||
```typescript
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [loading, setLoading] = useState(false); // inference: boolean
|
||||
|
||||
1. **User** met: id, name, email, avatar (optioneel), createdAt
|
||||
2. **Product** met: id, name, price, description (optioneel), inStock, category
|
||||
3. **Order** met: id, userId, products (array), total, status (pending/shipped/delivered)
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/users");
|
||||
const data: User[] = await response.json();
|
||||
setUsers(data);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
```
|
||||
|
||||
**Deel 3: React Component met Types (45 min)**
|
||||
---
|
||||
|
||||
Bouw een `UserList` component:
|
||||
- Props: users array, onSelectUser callback
|
||||
- State: selectedUserId (number of null)
|
||||
- Toon lijst van users, highlight geselecteerde
|
||||
### Event Handlers Typen (10 min)
|
||||
- `React.ChangeEvent<HTMLInputElement>`
|
||||
- `React.FormEvent<HTMLFormElement>`
|
||||
- `React.MouseEvent<HTMLButtonElement>`
|
||||
- Tip: hover in Cursor om het juiste event type te vinden
|
||||
|
||||
Alle types moeten correct zijn. Geen `any` gebruiken!
|
||||
```typescript
|
||||
function SearchForm() {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
### Deliverable
|
||||
- ProductCard.tsx met correcte types
|
||||
- types.ts met alle interfaces
|
||||
- UserList.tsx volledig getypt
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
console.log("Searching:", query);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input value={query} onChange={handleChange} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### API Responses & Async Typen (15 min)
|
||||
- `Promise<T>` voor async functies
|
||||
- API response types definiëren
|
||||
- Error handling met types
|
||||
- Fetch wrapper met generics
|
||||
|
||||
```typescript
|
||||
interface ApiResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Gebruik
|
||||
const { data: users } = await fetchApi<User[]>("/api/users");
|
||||
const { data: product } = await fetchApi<Product>("/api/products/1");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Cursor (Student Plan)
|
||||
- TypeScript
|
||||
- React (via CDN of Vite)
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (75 min)
|
||||
|
||||
### Typed React Dashboard
|
||||
|
||||
Studenten bouwen een kleine React-app met volledig getypte components:
|
||||
|
||||
**Opdracht:** Bouw een Product Dashboard met:
|
||||
|
||||
1. **`ProductCard` component** — props: Product interface, onAddToCart callback
|
||||
2. **`ProductList` component** — props: Product[], filterCategory (union type)
|
||||
3. **`SearchBar` component** — props: query string, onChange handler (getypt event)
|
||||
4. **`useProducts` custom hook** — fetch products, return `{ products, loading, error }`
|
||||
5. **Alle types in een apart `types.ts` bestand**
|
||||
|
||||
**Vereisten:**
|
||||
- Geen `any` toegestaan
|
||||
- Alle event handlers correct getypt
|
||||
- useState met expliciete types waar nodig
|
||||
- Minstens 1 generic functie (bijv. een `sortBy<T>` of `filterBy<T>`)
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### TypeScript Verdieping
|
||||
### Extend het Dashboard
|
||||
|
||||
**Deel 1: Drie Components Bouwen (1 uur)**
|
||||
Bouw voort op de lesopdracht:
|
||||
|
||||
Bouw volledig in TypeScript:
|
||||
|
||||
1. **SearchInput** component
|
||||
- Props: value, onChange, placeholder (optioneel)
|
||||
- Volledig getypt
|
||||
|
||||
2. **DataTable** component
|
||||
- Generic: werkt met elk type data
|
||||
- Props: data array, columns config
|
||||
- Type-safe rendering
|
||||
|
||||
3. **Modal** component
|
||||
- Props: isOpen, onClose, title, children
|
||||
- Correct gebruik van React.ReactNode
|
||||
|
||||
**Deel 2: Eindproject Interfaces (30 min)**
|
||||
|
||||
Bedenk de data structuur voor je eindproject:
|
||||
- Welke entiteiten heb je? (users, posts, products, etc.)
|
||||
- Maak interface voor elke entiteit
|
||||
- Documenteer relaties tussen entiteiten
|
||||
|
||||
**Deel 3: Cheat Sheet (30 min)**
|
||||
|
||||
Maak persoonlijke TypeScript cheat sheet:
|
||||
- Meest gebruikte types
|
||||
- Interface vs Type wanneer
|
||||
- Common patterns met React
|
||||
- Hoe je errors oplost
|
||||
1. **Shopping Cart** toevoegen met getypte state (`CartItem[]`)
|
||||
2. **API simulatie** — maak een `fetchProducts()` functie met `Promise<Product[]>`
|
||||
3. **Utility types gebruiken** — `Partial<Product>` voor updates, `Omit<Product, "id">` voor create
|
||||
4. **Bonus: Generic `DataTable<T>` component** — werkt met elke array van objecten
|
||||
|
||||
### Deliverable
|
||||
- 3 TypeScript components
|
||||
- types/index.ts met eindproject interfaces
|
||||
- TypeScript cheat sheet (1 pagina)
|
||||
- Werkend React project met TypeScript
|
||||
- Alle components volledig getypt
|
||||
- `npm run check` = 0 errors
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Uitleggen waarom TypeScript waardevol is
|
||||
- Basic types gebruiken (string, number, boolean, arrays)
|
||||
- Interfaces en type aliases schrijven
|
||||
- React components typen met props
|
||||
- useState met types gebruiken
|
||||
- Generics op basisniveau begrijpen
|
||||
- JavaScript code omzetten naar TypeScript
|
||||
- TypeScript errors lezen en oplossen
|
||||
- Generics schrijven en toepassen
|
||||
- Utility types gebruiken (Partial, Pick, Omit, Record)
|
||||
- React component props correct typen
|
||||
- useState en useEffect met types gebruiken
|
||||
- Event handlers typen (ChangeEvent, FormEvent, MouseEvent)
|
||||
- Async functies en API responses typen met Promise<T>
|
||||
- Een custom hook schrijven met correcte return types
|
||||
|
||||
Reference in New Issue
Block a user