Kurs vibe coding z AI · Moduł 2: Next.js Setup
TypeScript dla Next.js - Typy i Interfejsy
W tej lekcji15
Twój projekt Next.js używa TypeScript - to jak JavaScript z supermocami! Dodaje typy, które pomagają unikać błędów.
Czym jest TypeScript?
TypeScript to JavaScript z systemem typów. Kod TypeScript jest kompilowany do zwykłego JavaScript.
// JavaScript - brak typów
function greet(name) {
return "Hello " + name;
}
greet(123); // Działa, ale błąd logiczny!// TypeScript - z typami
function greet(name: string) {
return "Hello " + name;
}
greet(123); // Error: Argument of type 'number' not assignable to 'string'Dlaczego TypeScript?
- Bezpieczeństwo - błędy wychwytywane przed uruchomieniem
- Intellisense - podpowiedzi w edytorze
- Dokumentacja - typy są dokumentacją
- Refactoring - łatwiejsze zmiany w kodzie
- Domyślny w Next.js - create-next-app tworzy projekt w TypeScript, jeśli nie wybierzesz inaczej
Podstawowe typy w TypeScript
1. Typy prymitywne
// String
const shipName: string = "Black Pearl";
const captain: string = "Jack Sparrow";
// Number
const crewCount: number = 42;
const treasureValue: number = 1000000;
// Boolean
const isSailing: boolean = true;
const hasKraken: boolean = false;
// Undefined i Null
let treasure: number | undefined; // Może być number lub undefined
let kraken: null | object = null;2. Arrays (Tablice)
// Array liczb
const levels: number[] = [1, 2, 3, 4, 5];
// Array stringów
const pirates: string[] = ["Jack", "Anne", "Blackbeard"];
// Alternatywna składnia
const ships: Array<string> = ["Pearl", "Dutchman"];
// Array mieszany (union type)
const mixed: (string | number)[] = ["Jack", 42, "Anne", 7];3. Objects (Obiekty)
// Inline type
const pirate: {
name: string;
level: number;
ship: string;
} = {
name: "Jack Sparrow",
level: 10,
ship: "Black Pearl"
};
// Problem: musimy powtarzać typy dla każdego pirata!Interfejsy - Definiowanie struktur
Interface to jak blueprint (plan) dla obiektów - definiuje kształt danych.
Podstawowy interface
interface Pirate {
name: string;
level: number;
ship: string;
isActive: boolean;
}
// Teraz możemy używać tego typu wielokrotnie
const jack: Pirate = {
name: "Jack Sparrow",
level: 10,
ship: "Black Pearl",
isActive: true
};
const anne: Pirate = {
name: "Anne Bonny",
level: 8,
ship: "Revenge",
isActive: true
};
// Tablica piratów
const crew: Pirate[] = [jack, anne];Optional properties (opcjonalne właściwości)
Znak ? oznacza, że właściwość jest opcjonalna:
interface Ship {
name: string;
captain: string;
crewCount: number;
treasure?: number; // Opcjonalne - może być undefined
flag?: string; // Opcjonalne
}
const pearl: Ship = {
name: "Black Pearl",
captain: "Jack Sparrow",
crewCount: 42
// treasure i flag są opcjonalne - można pominąć
};
const dutchman: Ship = {
name: "Flying Dutchman",
captain: "Davy Jones",
crewCount: 100,
treasure: 1000000 // Mamy treasure tym razem
};Interface dla funkcji
interface GreetFunction {
(name: string): string;
}
const greetPirate: GreetFunction = (name) => {
return `Ahoy, ${name}!`;
};
console.log(greetPirate("Jack")); // "Ahoy, Jack!"Type vs Interface
TypeScript ma dwa sposoby definiowania typów: type i interface.
Type
type Pirate = {
name: string;
level: number;
};
type Ship = {
name: string;
captain: string;
};Interface
interface Pirate {
name: string;
level: number;
}
interface Ship {
name: string;
captain: string;
}Różnice:
| Feature | Type | Interface |
|---|---|---|
| Extends | Tak (z &) | Tak (z extends) |
| Union types | Tak | Nie |
| Primitives | Tak | Nie |
| Re-open | Nie | Tak |
Kiedy używać:
- Interface - dla obiektów, React components props
- Type - dla union types, primitives, złożonych typów
Union types - Typ "lub"
// Typ może być string LUB number
type ID = string | number;
const userId1: ID = "abc123"; // OK
const userId2: ID = 42; // OK
// Typ może być kilka konkretnych wartości
type Status = "pending" | "sailing" | "docked" | "sunk";
const shipStatus: Status = "sailing"; // OK
const wrongStatus: Status = "flying"; // Error!Typy w React Components
Props interface
// Definiujemy typy props
interface ButtonProps {
text: string;
onClick: () => void;
variant?: "primary" | "secondary"; // Opcjonalne
}
// Komponent z typowanymi props
function Button({ text, onClick, variant = "primary" }: ButtonProps) {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{text}
</button>
);
}
// Użycie
<Button text="Ahoj!" onClick={() => console.log("Clicked!")} />Children prop
interface CardProps {
title: string;
children: React.ReactNode; // Typ dla children
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// Użycie
<Card title="Pirate Card">
<p>This is content inside card</p>
</Card>Event handlers
interface FormProps {
onSubmit: (email: string) => void;
}
function Form({ onSubmit }: FormProps) {
const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get("email") as string;
onSubmit(email);
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" />
<button type="submit">Join crew</button>
</form>
);
}Generics - Uniwersalne typy
Generics pozwalają tworzyć komponenty/funkcje działające z różnymi typami.
// Generic function
function getFirstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstNumber = getFirstItem([1, 2, 3]); // number | undefined
const firstString = getFirstItem(["a", "b"]); // string | undefined
// Generic interface
interface Response<T> {
data: T;
status: number;
message: string;
}
interface Pirate {
name: string;
level: number;
}
const pirateResponse: Response<Pirate> = {
data: { name: "Jack", level: 10 },
status: 200,
message: "Success"
};
const stringResponse: Response<string> = {
data: "Hello",
status: 200,
message: "OK"
};TypeScript w Next.js - Praktyczne przykłady
Page component
// app/pirates/page.tsx
interface Pirate {
id: number;
name: string;
ship: string;
level: number;
}
export default function PiratesPage() {
const pirates: Pirate[] = [
{ id: 1, name: "Jack Sparrow", ship: "Black Pearl", level: 10 },
{ id: 2, name: "Anne Bonny", ship: "Revenge", level: 8 }
];
return (
<div>
<h1>Pirates</h1>
{pirates.map((pirate) => (
<div key={pirate.id}>
<h2>{pirate.name}</h2>
<p>Ship: {pirate.ship}</p>
<p>Level: {pirate.level}</p>
</div>
))}
</div>
);
}API Route handler
// app/api/pirates/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Pirate {
id: number;
name: string;
level: number;
}
export async function GET(request: NextRequest) {
const pirates: Pirate[] = [
{ id: 1, name: "Jack Sparrow", level: 10 },
{ id: 2, name: "Anne Bonny", level: 8 }
];
return NextResponse.json({ pirates });
}
export async function POST(request: NextRequest) {
const body: Pirate = await request.json();
// Walidacja typu
if (!body.name || typeof body.level !== 'number') {
return NextResponse.json(
{ error: "Invalid data" },
{ status: 400 }
);
}
return NextResponse.json({ success: true, pirate: body });
}Metadata type
// app/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: "Kraken's Call - Join the Adventure",
description: "Epic pirate game built with Next.js",
keywords: ["pirate", "game", "adventure"],
};
export default function HomePage() {
return <div>Welcome aboard!</div>;
}Type Assertions (as)
Czasami wiesz więcej o typie niż TypeScript:
interface Pirate {
name: string;
level: number;
}
// localStorage zwraca string | null
const savedData = localStorage.getItem("pirate");
// Mówimy TypeScript: "wiem, że to string"
const pirateData = savedData as string;
// Lub dla JSON
const pirate = JSON.parse(pirateData) as Pirate;
// Asercja typu dla elementu DOM
const element = document.getElementById("root") as HTMLDivElement;Uwaga: Używaj as ostrożnie - możesz oszukać TypeScript i dostać runtime error!
Utility Types - Gotowe narzędzia
TypeScript ma wbudowane utility types:
interface Pirate {
name: string;
level: number;
ship: string;
treasure: number;
}
// Partial - wszystkie właściwości opcjonalne
type PartialPirate = Partial<Pirate>;
// { name?: string; level?: number; ship?: string; treasure?: number; }
// Pick - wybierz tylko niektóre właściwości
type PiratePreview = Pick<Pirate, "name" | "level">;
// { name: string; level: number; }
// Omit - wyklucz niektóre właściwości
type PirateWithoutTreasure = Omit<Pirate, "treasure">;
// { name: string; level: number; ship: string; }
// Required - wszystkie właściwości wymagane
type RequiredPirate = Required<PartialPirate>;
// { name: string; level: number; ship: string; treasure: number; }any vs unknown vs never
// any - wyłącza sprawdzanie typów (unikaj!)
let anything: any = "hello";
anything = 42;
anything.nonExistent(); // Żadnego błędu! Danger!
// unknown - bezpieczniejsza wersja any
let something: unknown = "hello";
// something.toUpperCase(); // Error! Musimy sprawdzić typ
if (typeof something === "string") {
something.toUpperCase(); // OK!
}
// never - typ który nigdy nie występuje
function throwError(message: string): never {
throw new Error(message);
// Funkcja nigdy nie zwraca wartości - zawsze rzuca błąd
}tsconfig.json - Konfiguracja TypeScript
Twój projekt ma plik tsconfig.json z konfiguracją TypeScript:
{
"compilerOptions": {
"target": "ES2017", // Wersja JS, do której kompilować
"lib": ["dom", "dom.iterable", "esnext"], // Dostępne biblioteki
"jsx": "react-jsx", // Sposób kompilacji JSX (tak ustawia Next.js 16)
"module": "esnext", // System modułów
"moduleResolution": "bundler", // Importy rozwiązywane jak w bundlerze
"strict": true, // Ścisłe sprawdzanie typów
"esModuleInterop": true, // Kompatybilność z ES modules
"skipLibCheck": true, // Przyspiesza kompilację
"noEmit": true, // TypeScript tylko sprawdza, kompiluje Next.js
"paths": { "@/*": ["./*"] } // Alias @/ w importach
}
}Ważne opcje:
strict: true- włącza wszystkie ścisłe sprawdzenia (ZALECANE!)noImplicitAny: true- nie pozwala naanybez wyraźnego określeniastrictNullChecks: true- sprawdza czy wartość może być null/undefined
Używanie AI do TypeScript
Cursor świetnie radzi sobie z TypeScript! Zapytaj AI:
Prompt 1:
Stwórz interface dla karty pirata z imieniem, poziomem, statkiem i tablicą umiejętnościPrompt 2:
Jak stypować props dla React component który przyjmuje tytuł, opis i funkcję onClick?Prompt 3:
Popraw błędy TypeScript w tym kodzie: [wklej kod]Debugowanie błędów TypeScript
Częste błędy:
1. Property does not exist
const pirate = { name: "Jack" };
console.log(pirate.level); // Property 'level' does not exist
// Fix: dodaj właściwość do interface
interface Pirate {
name: string;
level?: number; // Opcjonalne
}2. Type 'X' is not assignable to type 'Y'
const level: number = "10"; // Błąd: string ≠ number
// Fix: konwertuj typ
const fixedLevel: number = parseInt("10"); // OK3. 'first' is possibly 'undefined' (w starszych wersjach TypeScript: Object is possibly 'undefined')
const pirates = [{ name: "Jack" }];
const first = pirates.find(p => p.name === "Jack");
console.log(first.name); // first może być undefined!
// Fix: optional chaining
console.log(first?.name); // OK
// Lub sprawdzenie
if (first) {
console.log(first.name); // OK
}Podsumowanie
TypeScript - JavaScript z typami, więcej bezpieczeństwa
Typy podstawowe - string, number, boolean, array
Interface - definiowanie struktury obiektów
Type vs Interface - interface dla obiektów, type dla union types
Union types - typ "lub" (string | number)
Generics - uniwersalne typy (<T>)
Props typing - interfejsy dla React components
Utility Types - Partial, Pick, Omit, Required
tsconfig.json - konfiguracja TypeScript
AI pomaga - Cursor wspiera TypeScript out-of-the-box
Następne kroki: W kolejnych modułach będziemy używać TypeScript w każdym komponencie. Nauczysz się typować:
- Server Components
- API routes
- Database queries
- Forms
- Hooks
Do zobaczenia!
Kod do tej lekcji: App.tsx
// Kraken's Call - karty funkcji opisane typami TypeScript
interface Feature {
title: string;
description: string;
isNew?: boolean; // pole opcjonalne
}
interface FeatureCardProps {
feature: Feature;
}
export default function App() {
return (
<main style={{ minHeight: "100vh", background: "#0f172a", padding: 32, fontFamily: "system-ui, sans-serif" }}>
<h1 style={{ color: "white", marginTop: 0 }}>Kraken's Call</h1>
<div style={{ display: "grid", gap: 16 }}>
{features.map((feature) => (
<FeatureCard key={feature.title} feature={feature} />
))}
</div>
</main>
);
}
const features: Feature[] = [
{ title: "Szybkie wiadomości", description: "Wysyłaj wiadomości szybciej niż statek pod pełnymi żaglami", isNew: true },
{ title: "Mapy skarbów", description: "Udostępniaj lokalizacje skarbów swojej załodze" },
{ title: "Papugi AI", description: "Inteligentne papugi pomagają w komunikacji" },
];
function FeatureCard({ feature }: FeatureCardProps) {
return (
<div style={{ background: "#1f2937", color: "white", padding: 16, borderRadius: 8 }}>
<h3 style={{ margin: "0 0 8px" }}>
{feature.title} {feature.isNew && <small style={{ color: "#fbbf24" }}>nowość</small>}
</h3>
<p style={{ margin: 0, color: "#9ca3af" }}>{feature.description}</p>
</div>
);
}Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czym jest TypeScript?
2. Do czego służy interface w TypeScript?
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż zapis typowanej zmiennej tekstowej w TypeScript (od lewej do prawej):
- Klikanie w kolejności
Zbuduj opcjonalne pole 'treasure' typu number w interface: