Kihagyás

TypeScript típusok

A gyakorlat anyaga

Alap típusok

Az alapvető típusokat már láttuk az előző gyakorlaton, túl sok újdonság nincs bennük a látottakon felül.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let age: number = 25;

let hexColor: number = 0xFF5733;  // 16744243
let binary: number = 0b1010;       // 10
let octal: number = 0o744;         // 484

// Special values
let infinity: number = Infinity;
let notANumber: number = NaN;

function calculateTotal(price: number, quantity: number): number {
    return price * quantity;
}

const total = calculateTotal(29.99, 3);  // 89.97
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let username: string = "alice_dev";

let description: string = `
    This is a multi-line
    description using
    template literals.
`;

function formatUserInfo(firstName: string, lastName: string): string {
    return `${firstName} ${lastName}`.toUpperCase();
}
console.log(formatUserInfo("James", "John"));
console.log("Jellicle cats".charAt(2));
console.log("Jellicle cats".endsWith('cats'));

const igaz: boolean = true;

Tömb típus

A tömbök azonos típusú (!) elemek gyűjteményét tárolják. Két szintaxis létezik, de a Type[] a preferált, az Array<Type> ritkábban használt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let userIds: number[] = [101, 102, 103, 104];
let tags: string[] = ["typescript", "javascript", "web"];
let isActive: boolean[] = [true, false, true];

// Other syntax (rarely used)
let productNames: Array<string> = ["Laptop", "Mouse", "Keyboard"];

function filterEvenNumbers(numbers: number[]): number[] {
    return numbers.filter(num => num % 2 === 0);
}

const evens = filterEvenNumbers([1, 2, 3, 4, 5, 6]);  // [2, 4, 6]

function calculateAverage(scores: number[]): number {
    if (scores.length === 0) return 0;

    const sum = scores.reduce((acc, score) => acc + score, 0);
    return sum / scores.length;
}

const avgScore = calculateAverage([85, 90, 78, 92, 88]);

// Multi-dimensional array
let matrix: number[][] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

Ha egy tömbből csak olvasni szeretnénk a kezdeti feltöltés után, érdemes a readonly módosítót használni, így garantálhatjuk, hogy a tömb nem módosítható. A kulcsszó a típus elé kerüljön, ahogy a lenti példán is látható.

1
2
const constants: readonly number[] = [3.14, 2.71, 1.41];
// constants.push(9.8);  // Error: Property 'push' does not exist

Union típus

A union típusok lehetővé teszik, hogy egy érték több típus egyike legyen. Az | operátorral jelöljük.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let id: string | number;
id = "USER123";  // OK
id = 456;        // OK
// id = true;    // Error

function formatId(id: string | number): string {
    if (typeof id === "string") {
        return id.toUpperCase();
    }
    return `ID-${id}`;
}

console.log(formatId("abc123"));  // "ABC123"
console.log(formatId(789));       // "ID-789"

Természetesen itt is lehet megadni null értéket, így vagy adott típust kapunk, vagy null-t.

1
2
3
4
5
6
7
8
9
function findUser(userId: number): string | null {
    const users = ["Alice", "Bob", "Charlie"];
    return users[userId] ?? null;
}

const user = findUser(0);
if (user !== null) {
    console.log(user.toUpperCase());  // TypeScript knows user is string
}

Arra is van lehetőségünk, hogy típusok helyett konkrétan értékeket adjunk csak meg, amelyek egyikét fel fogja venni az adott változó. Minden más esetben pedig hiba történik.

1
2
3
4
5
6
7
8
9
type ButtonSize = "small" | "medium" | "large"; // Literal union types
type Theme = "light" | "dark";

function createButton(size: ButtonSize, theme: Theme): void {
    console.log(`Creating ${size} button with ${theme} theme`);
}

createButton("medium", "dark");
// createButton("huge", "dark");  // Error: "huge" is not assignable

Discriminated Union (Megkülönböztető unió)

A discriminated union (más néven tagged union) egy speciális union típus minta, ahol minden típusnak van egy közös property-je (általában kind, type vagy tag néven), amely egyedileg azonosítja a típust. Ez a property teszi lehetővé a TypeScript számára, hogy automatikusan leszűkítse a típust, amikor ellenőrizzük az értékét.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// kind property!
type Shape = 
    | { kind: "circle"; radius: number }
    | { kind: "rectangle"; width: number; height: number }
    | { kind: "square"; size: number };

function calculateArea(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "rectangle":
            return shape.width * shape.height;
        case "square":
            return shape.size ** 2;
    }
}

const myCircle: Shape = { kind: "circle", radius: 5 };
const mySquare: Shape = { kind: "square", size: 10 };

console.log(calculateArea(myCircle));  // 78.54
console.log(calculateArea(mySquare));  // 100

A union típusok rugalmas és típusbiztos megoldást nyújtanak. A discriminated union minta egy kind vagy type mezővel teszi tisztábbá a típus leszűkítést is. Ahogy látszik is, ezek használata azért nem kifejezetten egyszerű, azonban a típusbiztosság miatt gyakran megéri.

Type alias (Típus alias)

A type kulcsszóval egyedi típusneveket hozhatunk létre, amelyek egyszerűbbé és olvashatóbbá teszik a kódot. A type alias-ok különösen hasznosak komplex típusok esetén, union típusoknál, objektum struktúrák definiálásánál, és függvény szignatúrák megadásánál. A type alias-ok nem hoznak létre új típust, csak egy új nevet adnak egy meglévő típusnak vagy típuskombinációnak.

Nagyon hasznosak gyakran használt union típusoknál is.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type UserId = string | number;

let id1: UserId = "ABC123";
let id2: UserId = 456;


type Status = "pending" | "approved" | "rejected" | "cancelled";

function updateStatus(orderId: number, status: Status): void {
    console.log(`Order ${orderId} is now ${status}`);
}

updateStatus(123, "approved");  // OK
// updateStatus(123, "invalid");  // Error

Természetesen nem csak itt használhatjuk, sok esetben hasznos lehet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type User = {
    id: number;
    name: string;
    email: string;
    isActive: boolean;
};

const user: User = {
    id: 1,
    name: "Alice",
    email: "alice@example.com",
    isActive: true
};

Illetve komplex objektumok formáját is leírhatjuk velük.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type Product = {
    id: number;
    name: string;
    price: number;
    category: "electronics" | "clothing" | "food";
    tags?: string[];  // Optional property
};

const laptop: Product = {
    id: 1,
    name: "Laptop",
    price: 999.99,
    category: "electronics",
    tags: ["computer", "portable"]
};

Tetszőleges módon egymásba is ágyazhatjuk ezeket.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Address = {
    street: string;
    city: string;
    country: string;
};

type Employee = {
    id: number;
    name: string;
    address: Address;
    department: "IT" | "HR" | "Sales";
};

const employee: Employee = {
    id: 101,
    name: "Bob",
    address: {
        street: "Main St",
        city: "Budapest",
        country: "Hungary"
    },
    department: "IT"
};

Enum típus

Az enumokkal elnevezett konstansokat hozhatunk létre, nagyon hasonlítanak más nyelvekben lévő enum típusokra. A string enumok általában jobbak debuggoláshoz TypeScriptben.

Numerikus enumok esetén az egyes konstansokhoz tartozó értékek számértékek lesznek:

1
2
3
4
5
6
7
8
9
enum LogLevel {
    Debug,    // 0
    Info,     // 1
    Warning,  // 2
    Error     // 3
}

let currentLevel: LogLevel = LogLevel.Info;
console.log(currentLevel);  // 1

Természetesen arra is lehetőségünk van, hogy egyéni értékekkel lássuk el az egyes konstans értékeket.

1
2
3
4
5
6
7
8
enum HttpStatus {
    OK = 200,
    Created = 201,
    BadRequest = 400,
    Unauthorized = 401,
    NotFound = 404,
    ServerError = 500
}

Szöveges enumok létrehozására is van lehetőség. Ebben az esetben minden konstansnak egy szöveges értéket állítunk be.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}

function move(direction: Direction): void {
    console.log(`Moving ${direction}`);
}

move(Direction.Up);  // "Moving UP"

Any típus

Az any típus kikapcsolja a típusellenőrzést. Ezt érdemes kerülni az esetek jelentős részében - ez felülírja a TypeScript alapvető célját.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let data: any = "hello";
data = 42;        // No error
data = true;      // No error
data = [1, 2, 3]; // No error

// Problem: runtime errors slip through
let value: any = "not a number";
let result: number = value * 2;  // Runtime error, but TypeScript allows it, will be NaN
console.log(result);

// Mixed type array with any keyword, dangerous
let mixedArray: any[] = [1, "text", true, { key: "value" }];

// Example of using any in the wrong way
function processData(input: any): void {
    console.log(input.toUpperCase());  // No TypeScript error!
}

processData("hello");  // Works fine
// processData(123);      // TypeError: input.toUpperCase is not a function

// Valid use case: gradual migration from JavaScript
// Converting old code to TypeScript
function legacyFunction(oldParam: any): any {
    // TODO: Add types after understanding the data flow
    return oldParam;
}

Érdemes az any helyett a unknown-t használni, vagy pedig valódi típusokat, amennyiben tudjuk azokat. Egyetlen kivétel ez alól az, amikor a létező JavaScript kódbázisunkat migráljuk TypeScriptre. Ebben az esetben a kezdeti átírás lehet például any, de ezt a lehető legrövidebb időn belül érdemes lecserélni.

Az any szűrésére több eszköz is létezik, például az ESLint elemzőnek is van ide vonatkozó szabálya. Vagy például a noImplicitAny konfigurációt be lehet kapcsolni a tsconfig.json-ben

Void típus

A void típus azt jelzi, hogy egy függvény semmit sem ad vissza. Kizárólag függvény visszatérési típusokhoz használjuk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function logMessage(message: string): void {
    console.log(message);
    // No need for a return statement
}

// Real-world example: event handler
function handleButtonClick(event: MouseEvent): void {
    event.preventDefault();
    console.log("Button clicked!");
    // Performs operations but doesn't return anything
}

// Common mistake
// function incorrectVoid(): void {
//     return 42;  // Error: Type 'number' is not assignable to type 'void'
// }

// Correct way to use void: it can return undefined or with an empty return
function correctVoid(): void {
    if (Math.random() > 0.5) {
        return;  // OK: empty return
    }
    console.log("Doing something");
}

És bizony, tömbökkel kapcsolatos, vagy bármilyen egyéb, callbacket használó művelet esetén is használhatunk típusokat, például az arrow function-ökben is.

1
2
3
4
5
const numbers: number[] = [1, 2, 3, 4, 5];

numbers.forEach((num: number): void => {
    console.log(num * 2);
});

Never típus

A never típus olyan értékeket reprezentál, amelyek soha nem következnek be. Használatos hibát dobó függvényeknél és exhaustive type checking-nél (kimerítő típusellenőrzés, amikor az összes lehetséges típust ellenőrizzük, és pl. egy default vagy else ágba garantáltan nem juthatunk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function throwError(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {
        console.log("Running forever...");
    }
}

// Exhaustive type checking
type Status = "pending" | "approved" | "rejected";

function handleStatus(status: Status): string {
    switch (status) {
        case "pending":
            return "Waiting for review";
        case "approved":
            return "Request approved";
        case "rejected":
            return "Request rejected";
        default:
            // Exhaustiveness check - compiler ensures all cases handled
            const _exhaustive: never = status;
            throw new Error(`Unhandled status: ${_exhaustive}`);
    }
}

A never típus biztosítja, hogy minden esetet kezelj bizonyos helyzetekben. Ha új típust adsz hozzá, de elfelejted kezelni, fordítási hibát kapsz.

Unknown típus

Az unknown típus az any típusbiztos alternatívája. Bármilyen értéket elfogad, de használat előtt típusellenőrzés szükséges.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// unknown accepts any value
let data: unknown = "hello";
data = 42;
data = true;
data = { key: "value" };

// Type checking is REQUIRED before use
// data.toUpperCase();  // Error: Object is of type 'unknown'

// Correct: with type checking
if (typeof data === "string") {
    console.log(data.toUpperCase());  // OK: TypeScript knows it's string
}

Az unknown használata biztosítja, hogy minden adatot validáljunk használat előtt, így elkerülhetjük a futási idejű hibákat.

Type Casting (Típuskényszerítés)

A type casting megmondja a TypeScript-nek, hogy egy értéket adott típusúként kezeljen. Óvatosan használjuk, mert megkerüli a típusbiztonságot.

1
2
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

Emellett használhatjuk a kacsacsőrökkel jelölt angle bracket szintaxist is, például (<string>someValue).length. Azonban ezt érdemes lehet kerülni, mert JSX/React kódokban nem fog működni (de ezzel ez a kurzus nem foglalkozik).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type User = {
    id: number;
    name: string;
    email: string;
}

let userData: unknown = {
    id: 1,
    name: "Alice",
    email: "alice@example.com"
};

// Cast to User type
let user = userData as User;
console.log(user.name);  // "Alice"

A type casting azonban NEM konvertál, erre nagyon figyeljünk! Csak a fordítónak mondjuk meg, hogyan kezelje az adott értéket, de ezzel nem garantáljuk, hogy valóban olyan típusúvá alakuljon az adott érték (ha valódi típuskonverziót szeretnénk, használjunk konverziós függvényeket).

1
2
3
let value: any = "123";
let num = value as number;
console.log(typeof num);  // "string" - it's still a string!

A TypeScript akkor a legerősebb, amikor együtt dolgozol a típusrendszerrel, nem ellene. A fordító segít, nem akadályoz!

Gyakorló feladatok

  1. Felhasználói jogosultságok

    Készíts egy UserRole type alias-t, amely csak a következő értékeket veheti fel: "admin", "editor", "viewer". Írj egy checkPermission függvényt, amely egy szerepkört és egy műveletet kap, és visszaadja string-ként, hogy a felhasználó végrehajthatja-e: - "admin" mindent megtehet - "editor" csak olvasni és szerkeszteni tud - "viewer" csak olvasni tud

    A művelet lehet: "read", "edit", "delete" (szintén készíts hozzá type alias-t).

  2. Termék katalógus

    Definiálj egy Product type alias-t a következő tulajdonságokkal:

    • id: number
    • name: string
    • price: number
    • inStock: boolean
    • category: "electronics" | "books" | "clothing"
    • description: opcionális string

    Írj egy függvényt, amely egy termék tömböt kap, és visszaadja csak azokat a termékeket, amelyek raktáron vannak.

  3. API válasz kezelő

    Egy API-tól különböző típusú válaszokat kaphatunk. Készíts discriminated union-t a következő válaszokra:

    • Sikeres válasz: { status: "success", data: any }
    • Hibaüzenet: { status: "error", message: string }
    • Betöltés állapot: { status: "loading" }

    Írj egy handleApiResponse függvényt, amely feldolgozza ezeket a válaszokat és megfelelő üzenetet ír ki console.log-ba.

  4. Rendelés státusz enum

    Készíts egy OrderStatus enumot a következő értékekkel:

    • Pending (érték: "PENDING")
    • Processing (érték: "PROCESSING")
    • Shipped (érték: "SHIPPED")
    • Delivered (érték: "DELIVERED")
    • Cancelled (érték: "CANCELLED")

    Írj egy getStatusMessage függvényt, amely egy OrderStatus-t kap, és visszaad egy magyar nyelvű leírást a státuszról.

  5. Bankszámla műveletek

    Készíts típusdefiníciókat egy banki rendszerhez: - Account type: accountNumber (string), balance (number), owner (string) - TransactionType union: "deposit" | "withdrawal" | "transfer" - Transaction type: type (TransactionType), amount (number), timestamp (Date), fromAccount? (optional string), toAccount? (optional string)

    Írj egy processTransaction függvényt, amely egy számlát és egy tranzakciót kap, és visszaadja az új számlaállást. Withdrawal esetén ellenőrizd, hogy van-e elég fedezet (ha nincs, térj vissza null-lal)!