Kurs vibe coding z AI · Moduł 2: Next.js Setup

TypeScript dla Next.js - Typy i Interfejsy

10 min czytania ·
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:

FeatureTypeInterface
ExtendsTak (z &)Tak (z extends)
Union typesTakNie
PrimitivesTakNie
Re-openNieTak

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 na any bez wyraźnego określenia
  • strictNullChecks: 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ści

Prompt 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");  // OK

3. '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. 1. Czym jest TypeScript?

  2. 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:

Przydatne artykuły