235 lines
6.1 KiB
Markdown
235 lines
6.1 KiB
Markdown
# Les 5: TypeScript voor React
|
||
|
||
---
|
||
|
||
## Hoofdstuk
|
||
**Deel 2: Technical Foundations** (Les 4-9)
|
||
|
||
## Beschrijving
|
||
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
|
||
|
||
### 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
|
||
// Generic functie
|
||
function wrapInArray<T>(value: T): T[] {
|
||
return [value];
|
||
}
|
||
|
||
wrapInArray("hello"); // string[]
|
||
wrapInArray(42); // number[]
|
||
|
||
// Met constraint
|
||
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||
return obj[key];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 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: string;
|
||
name: string;
|
||
email: string;
|
||
age: number;
|
||
}
|
||
|
||
// Partial: voor update functies
|
||
function updateUser(id: string, updates: Partial<User>): User { ... }
|
||
updateUser("1", { name: "Tim" }); // alleen name updaten
|
||
|
||
// Omit: voor create functies (id wordt server-side gegenereerd)
|
||
type CreateUserInput = Omit<User, "id">;
|
||
|
||
// Pick: voor specifieke views
|
||
type UserPreview = Pick<User, "id" | "name">;
|
||
```
|
||
|
||
---
|
||
|
||
### 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
|
||
interface CardProps {
|
||
title: string;
|
||
children: React.ReactNode;
|
||
variant?: "default" | "highlighted";
|
||
onClose?: () => void;
|
||
}
|
||
|
||
function Card({ title, children, variant = "default", onClose }: CardProps) {
|
||
return (
|
||
<div className={`card card-${variant}`}>
|
||
<h2>{title}</h2>
|
||
{onClose && <button onClick={onClose}>×</button>}
|
||
{children}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 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
|
||
|
||
```typescript
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [products, setProducts] = useState<Product[]>([]);
|
||
const [loading, setLoading] = useState(false); // inference: boolean
|
||
|
||
useEffect(() => {
|
||
async function fetchData() {
|
||
setLoading(true);
|
||
const response = await fetch("/api/users");
|
||
const data: User[] = await response.json();
|
||
setUsers(data);
|
||
setLoading(false);
|
||
}
|
||
fetchData();
|
||
}, []);
|
||
```
|
||
|
||
---
|
||
|
||
### 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
|
||
|
||
```typescript
|
||
function SearchForm() {
|
||
const [query, setQuery] = useState("");
|
||
|
||
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)
|
||
|
||
### Extend het Dashboard
|
||
|
||
Bouw voort op de lesopdracht:
|
||
|
||
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
|
||
- Werkend React project met TypeScript
|
||
- Alle components volledig getypt
|
||
- `npm run check` = 0 errors
|
||
|
||
---
|
||
|
||
## Leerdoelen
|
||
Na deze les kan de student:
|
||
- 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
|