third week

This commit is contained in:
Donát committed 2026-09-08 09:36:02 +02:00
1 parent 95e62c915f
commit a935851e22
31 files changed
+1269 -33

No files matched your search

+1
View File
@@ -179,6 +179,7 @@ megtekintéséhez!},icon=Paperclip}
} }
% Kisebb betűméret a slide-okhoz % Kisebb betűméret a slide-okhoz
\setbeamerfont{section in head/foot}{size=\tiny}
\setbeamerfont{frametitle}{size=\normalsize} \setbeamerfont{frametitle}{size=\normalsize}
\setbeamerfont{framesubtitle}{size=\small} \setbeamerfont{framesubtitle}{size=\small}
\setbeamerfont{block title}{size=\small} \setbeamerfont{block title}{size=\small}
@@ -0,0 +1,92 @@
\section{Bevezetés}
\begin{frame}{Miről szól ez az anyag?}
\footnotesize
Ez egy \textbf{önállóan is végigkövethető, kiegészítő} tananyag a TypeScriptről. Nem feltételezi, hogy láttad a 2. hét TypeScript diáit -- a legelejétől indulunk, majd jóval mélyebbre megyünk, mint amit egy heti óra kerete engedne.
\begin{itemize}
\item Ha még sosem láttál TypeScript kódot, nyugodtan kezdd itt
\item Ha a 2. héten már megismerted az alapokat, néhány dia ismétlés lesz -- utána viszont új anyag következik
\item A cél: hogy magabiztosan tudj TypeScript kódot olvasni és írni, ne csak felismerni
\end{itemize}
\begin{alertblock}{Nem kötelező anyag}
Ez a diasor \emph{nem} számonkérendő tananyag -- annak szól, aki jobban meg szeretné érteni a TypeScript-et, mint amire a heti órák ideje engedne.
\end{alertblock}
\end{frame}
\begin{frame}{Mi az a TypeScript, és miért érdemes használni?}
\footnotesize
A JavaScript \textbf{gyengén típusos} nyelv: egy változó bármikor bármilyen típusú értéket felvehet, és a legtöbb hiba csak futás közben derül ki. A \textbf{TypeScript} (Microsoft, 2012) a JavaScript egy kiterjesztése, ami \emph{statikus típusrendszert} ad hozzá -- a kód végül mindig sima JavaScript-té fordul le.
\begin{itemize}
\item A típushibák nagy része már íráskor / fordításkor kiderül, nem futás közben, esetleg egy élő felhasználónál
\item Az IDE (VS Code, stb.) pontos kódkiegészítést, azonnali hibajelzést tud adni
\item A kód önmagát dokumentálja: egy függvény szignatúrájából látszik, mit vár és mit ad vissza
\item Nagyobb csapatban, nagyobb, hosszú távon karbantartott kódbázisban különösen megtérül
\end{itemize}
\begin{block}{Fontos}
A TypeScript \emph{opcionális, fokozatosan bevezethető} réteg -- egy meglévő JavaScript projektbe fájlonként is átállhatunk rá.
\end{block}
\end{frame}
\begin{frame}[fragile]{Telepítés és a \texttt{tsconfig.json}}
\footnotesize
A TypeScript fordítót (\texttt{tsc}) npm csomagként telepítjük, majd egy konfigurációs fájlt hozunk létre, ami megmondja a fordítónak, hogyan viselkedjen.
\begin{block}{Telepítés}
\begin{verbatim}
npm install -D typescript
npx tsc --init
\end{verbatim}
\end{block}
\begin{block}{A \texttt{tsconfig.json} legfontosabb mezői}
\begin{itemize}
\item \texttt{target} -- milyen JavaScript verzióra fordítson (pl. \texttt{"ES2020"})
\item \texttt{module} -- modulrendszer (\texttt{"commonjs"} vagy \texttt{"esnext"})
\item \texttt{outDir} / \texttt{rootDir} -- hova kerüljön a lefordított kód, honnan olvasson forrást
\item \texttt{strict} -- bekapcsolja az összes szigorú típusellenőrzést (\emph{erősen ajánlott} \texttt{true})
\end{itemize}
\end{block}
\end{frame}
\begin{frame}[fragile]{Futtatás fejlesztés közben}
\footnotesize
A \texttt{.ts} fájlokat sem a böngésző, sem a Node.js nem futtatja közvetlenül -- előbb JavaScript-té kell fordítani. Fejlesztés közben ezt kényelmesebb eszközökkel oldjuk meg, mint kézzel fordítani minden mentés után.
\begin{block}{Klasszikus fordítás}
\begin{verbatim}
npx tsc # egyszeri forditas
npx tsc --watch # automatikusan ujrafordit mentes utan
\end{verbatim}
\end{block}
\begin{block}{Közvetlen futtatás (nincs külön fordítási lépés)}
\begin{verbatim}
npx tsx index.ts
\end{verbatim}
A \texttt{tsx} (és a régebbi \texttt{ts-node}) memóriában fordítja és rögtön futtatja is a kódot -- ez a legkényelmesebb fejlesztés közben.
\end{block}
\end{frame}
\begin{frame}[fragile]{Az első \texttt{.ts} fájl}
\footnotesize
Nézzük meg, hogyan néz ki egy egyszerű, típusozott TypeScript fájl -- ez lesz az alapja a következő diáknak, ahol részletesen végigvesszük az egyes típusokat.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function add(a: number, b: number): number {
return a + b;
}
const result = add(2, 3);
console.log(`Az eredmeny: ${result}`);
// add('2', 3); -- ez forditasi hibat adna!
\end{lstlisting}
\begin{block}{Amit érdemes megfigyelni}
A paraméterek és a visszatérési érték típusát kettősponttal adjuk meg. Ha rossz típusú értékkel próbálnánk hívni a függvényt, a fordító \emph{azonnal} hibát jelezne -- még mielőtt a kód lefutna.
\end{block}
\end{frame}
@@ -0,0 +1,99 @@
\section{Alaptípusok}
\begin{frame}[fragile]{Primitív típusok}
\footnotesize
A TypeScript primitív típusai megegyeznek a JavaScript primitív értékeivel -- csak most a fordító is számon tartja, melyik változó melyik típusú.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
let name: string = 'Anna';
let age: number = 25;
let isActive: boolean = true;
let id: bigint = 123n;
let nothing: null = null;
let notSet: undefined = undefined;
\end{lstlisting}
\begin{itemize}
\item \texttt{string}, \texttt{number}, \texttt{boolean} -- a leggyakrabban használt három típus
\item \texttt{null} és \texttt{undefined} önálló típusként is léteznek, de gyakrabban más típusokkal együtt (pl. \texttt{string | null}) fordulnak elő
\item \texttt{bigint} és \texttt{symbol} ritkábban használt, speciális típusok
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Tömbök és tuple-ök}
\footnotesize
A tömböknél meg kell adni, milyen típusú elemeket tartalmaznak. A \textbf{tuple} egy speciális, rögzített hosszúságú és típussorrendű tömb.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
let names: string[] = ['Anna', 'Bela'];
let scores: Array<number> = [10, 20, 30];
// Tuple: pontosan ket elem, elore rogzitett tipussal
let point: [number, number] = [3, 5];
let entry: [string, number] = ['age', 25];
\end{lstlisting}
\begin{block}{Miért hasznos a tuple?}
Egy \texttt{[string, number]} tuple azonnal jelzi, hogy pontosan két elemet várunk, meghatározott sorrendben -- ellentétben egy sima \texttt{(string | number)[]} tömbbel, ami bármilyen hosszú és sorrendű keveréket engedne.
\end{block}
\end{frame}
\begin{frame}[fragile]{\texttt{any}, \texttt{unknown} és \texttt{never}}
\footnotesize
Három speciális típus, amit érdemes megkülönböztetni -- könnyű összekeverni őket, de nagyon más a szerepük.
\begin{itemize}
\item \textbf{\texttt{any}:} teljesen kikapcsolja a típusellenőrzést erre a változóra -- gyakorlatilag "visszatérünk sima JavaScript-hez". Kerülendő, csak végső esetben használjuk!
\item \textbf{\texttt{unknown}:} szintén "bármi lehet", de \emph{használat előtt} típusellenőrzést (narrowing) kényszerít ki -- ez a biztonságos alternatívája az \texttt{any}-nek
\item \textbf{\texttt{never}:} olyan érték típusa, ami \emph{sosem} fordulhat elő -- pl. egy függvény, ami mindig hibát dob, vagy egy végtelen ciklus visszatérési típusa
\end{itemize}
\begin{alertblock}{Ökölszabály}
Ha típust nem tudsz vagy nem akarsz megadni, használj \texttt{unknown}-t \texttt{any} helyett -- így a fordító legalább kikényszeríti, hogy ellenőrizd az értéket, mielőtt használnád.
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Type inference -- amikor nem kell kiírni a típust}
\footnotesize
A TypeScript sok esetben \emph{magától kikövetkezteti} (infer) a típust a kezdőértékből -- nem kell mindenhol explicit típust írnunk.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
let name = 'Anna'; // TypeScript tudja: ez string
let age = 25; // TypeScript tudja: ez number
name = 42; // Hiba! nem lehet number-t string-nek adni
\end{lstlisting}
\begin{block}{Mikor írjunk mégis explicit típust?}
Függvényparamétereknél szinte mindig érdemes (a fordító nem tudja kitalálni, mit fogunk hívni), de egyszerű, azonnal értékadott lokális változóknál nyugodtan hagyatkozhatunk az inferálásra.
\end{block}
\end{frame}
\begin{frame}[fragile]{Type alias -- saját név egy típusnak}
\footnotesize
A \texttt{type} kulcsszóval nevet adhatunk egy típusnak -- ez különösen hasznos, ha egy összetettebb típust (pl. union típust, amit a 4. fejezetben veszünk) többször is használunk.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type UserId = number;
type Username = string;
function getUser(id: UserId): Username {
return 'Anna';
}
\end{lstlisting}
Ez még nem sokkal több, mint egy elnevezés -- de amint a következő fejezetekben összetettebb típusokat (union, interfész) ismerünk meg, a \texttt{type} igazán hasznossá válik.
\end{frame}
\begin{frame}{Összefoglalás -- Alaptípusok}
\footnotesize
\begin{block}{Alaptípusok}
\begin{itemize}
\item Primitívek: \texttt{string}, \texttt{number}, \texttt{boolean}, \texttt{null}, \texttt{undefined}, \texttt{bigint}
\item Tömbök: \texttt{string[]}; Tuple: rögzített hosszú/sorrendű tömb, pl. \texttt{[string, number]}
\item \texttt{any} (kerülendő) vs \texttt{unknown} (biztonságos) vs \texttt{never} (sosem előforduló érték)
\item Type inference: a fordító sokszor magától kitalálja a típust
\item \texttt{type} -- nevet adhatunk egy típusnak, újrafelhasználhatóvá téve azt
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,90 @@
\section{Interface/Type}
\begin{frame}[fragile]{Interfész alapok}
\footnotesize
Az \texttt{interface} egy objektum \emph{alakját} (mely mezők, milyen típussal) írja le. Ahol egy interfészt típusként megadunk, a fordító ellenőrzi, hogy az érték tényleg megfelel-e neki.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface User {
id: number;
name: string;
email: string;
nickname?: string; // opcionalis mezo
readonly createdAt: Date; // csak olvashato
}
const user: User = {
id: 1,
name: 'Anna',
email: 'anna@example.com',
createdAt: new Date(),
};
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]{Opcionális és csak olvasható mezők}
\footnotesize
Két apró, de nagyon hasznos jelölés van, amivel pontosabbá tehetjük az interfészünket: a \texttt{?} és a \texttt{readonly}.
\begin{itemize}
\item \textbf{\texttt{mezo?: tipus}} -- a mező elhagyható, de ha megadjuk, a megjelölt típusúnak kell lennie
\item \textbf{\texttt{readonly mezo: tipus}} -- a mező az objektum létrehozása után nem módosítható
\end{itemize}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const user: User = { id: 1, name: 'Anna',
email: 'a@b.com', createdAt: new Date() };
user.createdAt = new Date(); // Hiba! readonly mezo
\end{lstlisting}
\begin{block}{Miért hasznos?}
A \texttt{readonly} segít elkerülni azokat a hibákat, amikor véletlenül módosítunk egy olyan mezőt, aminek soha nem szabadna változnia (pl. egy azonosítót).
\end{block}
\end{frame}
\begin{frame}[fragile]{Interfészek kiterjesztése}
\footnotesize
Egy interfész \texttt{extends}-szel örökölheti egy másik interfész mezőit -- így elkerülhetjük az ismétlődést hasonló, egymásra épülő alakoknál.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
}
const myDog: Dog = { name: 'Buksi', age: 3, breed: 'labrador' };
\end{lstlisting}
A \texttt{Dog} interfész mindent örököl az \texttt{Animal}-tól, és hozzáad egy \texttt{breed} mezőt -- pontosan úgy, ahogy egy osztály is örökölhet egy másiktól.
\end{frame}
\begin{frame}{\texttt{interface} vs \texttt{type} -- mikor melyiket?}
\footnotesize
A két eszköz sokban hasonlít, és a legtöbb egyszerű esetben szinte mindegy, melyiket választjuk -- de van néhány különbség, ami eldöntheti a választást.
\begin{itemize}
\item \textbf{\texttt{interface}:} csak objektum alakok leírására való; több, azonos nevű interfész deklaráció automatikusan összeolvad (declaration merging); \texttt{extends}-szel bővíthető
\item \textbf{\texttt{type}:} bármilyen típust elnevezhet -- objektumot, uniót, primitívet is; nem olvad össze automatikusan, de intersection-nel (\texttt{\&}) kombinálható
\end{itemize}
\begin{block}{Gyakorlati ökölszabály}
Objektum \emph{alakokhoz} (pl. egy adatmodell) válaszd az \texttt{interface}-t -- olvashatóbb hibaüzeneteket ad. Union típusokhoz (a következő fejezet) csak a \texttt{type} jöhet szóba.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Interface és Type}
\footnotesize
\begin{block}{Interface és Type}
\begin{itemize}
\item \texttt{interface}: objektum alakjának leírása -- mezők, opcionális (\texttt{?}) és readonly mezők
\item \texttt{extends}: interfészek egymásra építése, kódismétlés elkerülése
\item \texttt{type}: bármilyen típusnak adhatunk nevet, nemcsak objektumoknak
\item Objektum alakoknál \texttt{interface}, union típusoknál \texttt{type} -- ökölszabályként
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,88 @@
\section{Függvények}
\begin{frame}[fragile]{Függvények típusozása}
\footnotesize
Egy TypeScript függvénynél mind a paraméterek, mind a visszatérési érték típusát megadhatjuk -- a visszatérési típust a fordító a legtöbbször ki is következteti, de explicit kiírva olvashatóbb, és véd az elgépelés ellen.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function multiply(a: number, b: number): number {
return a * b;
}
// Nincs visszatero ertek -> void
function logMessage(message: string): void {
console.log(message);
}
\end{lstlisting}
\begin{block}{Miért érdemes kiírni a visszatérési típust?}
Ha véletlenül rossz típusú értéket adnánk vissza (pl. \texttt{string}-et \texttt{number} helyett), a fordító azonnal jelezné a hibát -- még mielőtt bárhol meghívnánk a függvényt.
\end{block}
\end{frame}
\begin{frame}[fragile]{Opcionális és alapértelmezett paraméterek}
\footnotesize
Nem minden paraméter kötelező -- a JavaScript-hez hasonlóan itt is megadhatunk alapértelmezett értéket, vagy jelölhetjük egy paramétert opcionálisként.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function greet(name: string, greeting: string = 'Szia'): string {
return `${greeting}, ${name}!`;
}
function createUser(name: string, nickname?: string) {
return { name, nickname: nickname ?? name };
}
greet('Anna'); // "Szia, Anna!"
greet('Anna', 'Hello'); // "Hello, Anna!"
\end{lstlisting}
\begin{alertblock}{Fontos sorrend}
Az opcionális (\texttt{?}) és alapértelmezett paraméterek csak a kötelező paraméterek \emph{után} szerepelhetnek a paraméterlistában.
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Függvénytípusok -- callback-ek típusozása}
\footnotesize
Amikor egy függvény egy másik függvényt vár paraméterként (pl. egy callback-et), magának a "függvény alaknak" is megadhatjuk a típusát.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type MathOperation = (a: number, b: number) => number;
function calculate(a: number, b: number, op: MathOperation): number {
return op(a, b);
}
const sum: MathOperation = (a, b) => a + b;
calculate(2, 3, sum); // 5
\end{lstlisting}
Ez különösen hasznos Express.js-ben: egy route handler is pontosan egy ilyen "függvény alakú" típus (\texttt{(req, res) => void}).
\end{frame}
\begin{frame}{Function overloads -- röviden}
\footnotesize
Néha egy függvény többféle paraméter-kombinációval is hívható, és a visszatérési típus a bemenettől függ. Erre valók a \textbf{overloads}: több szignatúra deklarálása egyetlen implementáció előtt.
\begin{itemize}
\item Több "fejlécet" (szignatúrát) írunk le, majd egyetlen tényleges implementációt
\item A hívó fél oldaláról úgy tűnik, mintha több, különböző típusú változata lenne a függvénynek
\item Ritkán szükséges kezdőként -- inkább unió típusokkal (következő fejezet) old meg a legtöbb esetet
\end{itemize}
\begin{block}{Gyakorlati tanács}
Ha csak most ismerkedsz a TypeScript-tel, nem kell aktívan használnod az overloads-ot -- elég felismerni, ha egy könyvtár dokumentációjában találkozol vele.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Függvények}
\footnotesize
\begin{block}{Függvények TypeScript-ben}
\begin{itemize}
\item Paraméterek és visszatérési típus típusozása -- \texttt{void}, ha nincs visszatérő érték
\item Alapértelmezett érték (\texttt{= ...}) és opcionális paraméter (\texttt{?}), mindig a kötelezők után
\item Függvénytípusok: \texttt{(a: number, b: number) => number} -- callback-ek típusozásához
\item Overloads: több szignatúra egy implementációhoz -- ritkán szükséges kezdőként
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,90 @@
\section{Union/Intersection}
\begin{frame}[fragile]{Union típusok -- "vagy ez, vagy az"}
\footnotesize
Egy \textbf{union} típus (\texttt{|} jellel) azt fejezi ki, hogy egy érték \emph{többféle} típus egyike lehet. Ez az egyik legtöbbet használt eszköz a valós TypeScript kódban.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type Id = string | number;
function printId(id: Id) {
console.log(`Az azonosito: ${id}`);
}
printId(123); // OK
printId('abc-123'); // OK is
\end{lstlisting}
\begin{block}{Gyakori példa}
\texttt{type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';} -- ilyenkor a fordító nem enged elgépelt, érvénytelen metódusnevet.
\end{block}
\end{frame}
\begin{frame}[fragile]{Type narrowing -- a típus leszűkítése}
\footnotesize
Ha egy változó union típusú, a fordító csak azokat a műveleteket engedi rajta, amik \emph{minden} lehetséges típusnál érvényesek -- amíg nem szűkítjük le (narrowing), melyik konkrét típusról van szó.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function printId(id: string | number) {
if (typeof id === 'string') {
console.log(id.toUpperCase()); // itt mar biztos string
} else {
console.log(id.toFixed(2)); // itt mar biztos number
}
}
\end{lstlisting}
\begin{itemize}
\item \texttt{typeof} -- primitív típusok megkülönböztetésére (\texttt{string}, \texttt{number}, stb.)
\item \texttt{instanceof} -- osztálypéldányok megkülönböztetésére
\item \texttt{in} -- egy adott mező meglétének ellenőrzésére egy objektumon
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Intersection típusok -- "ez is, az is"}
\footnotesize
Az \textbf{intersection} típus (\texttt{\&} jellel) ennek épp az ellentéte: egy érték \emph{egyszerre} feleljen meg több típusnak is -- a mezők összeadódnak.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface HasId {
id: number;
}
interface HasName {
name: string;
}
type Entity = HasId & HasName;
const user: Entity = { id: 1, name: 'Anna' };
\end{lstlisting}
Az \texttt{Entity} típusú értéknek \emph{mindkét} interfész mezőit tartalmaznia kell -- ez hasznos, ha kisebb, újrafelhasználható "építőelemekből" rakunk össze nagyobb típusokat.
\end{frame}
\begin{frame}[fragile]{Literal típusok}
\footnotesize
A TypeScript-ben nemcsak \texttt{string} vagy \texttt{number} lehet egy típus, hanem egy \emph{konkrét érték} is -- ezt hívjuk literal típusnak. Union-nal kombinálva rendkívül kifejező, önmagát dokumentáló kódot kapunk.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type Status = 'pending' | 'active' | 'closed';
function setStatus(status: Status) {
// csak a harom felsorolt string-et fogadja el
}
setStatus('active'); // OK
setStatus('done'); // Hiba! nincs ilyen Status
\end{lstlisting}
\end{frame}
\begin{frame}{Összefoglalás -- Union és Intersection}
\footnotesize
\begin{block}{Union és Intersection típusok}
\begin{itemize}
\item Union (\texttt{|}): "vagy ez, vagy az" -- egy érték többféle típus egyike lehet
\item Type narrowing: \texttt{typeof}/\texttt{instanceof}/\texttt{in} -- leszűkítjük, melyik konkrét típusról van szó
\item Intersection (\texttt{\&}): "ez is, az is" -- több típus mezői összeadódnak
\item Literal típusok: konkrét értékek típusként, gyakran union-nal kombinálva (pl. HTTP metódusok)
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,89 @@
\section{Generikusok}
\begin{frame}[fragile]{A probléma, amit a generikák oldanak meg}
\footnotesize
Képzeljünk el egy függvényt, ami egy tömb első elemét adja vissza -- de szeretnénk, hogy \emph{bármilyen} típusú tömbre működjön, miközben megőrizzük a típusinformációt.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function firstOfNumbers(arr: number[]): number {
return arr[0];
}
function firstOfStrings(arr: string[]): string {
return arr[0];
}
// ...es igy tovabb minden tipusra kulon fuggveny?
\end{lstlisting}
\begin{alertblock}{A probléma}
\texttt{any}-jal megoldhatnánk, de akkor elveszítenénk a típusbiztonságot: a fordító többé nem tudná, mit ad vissza a függvény.
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Generikus függvények}
\footnotesize
A \textbf{generikus típus} egy "típusparamétert" (jellemzően \texttt{T}) vezet be, amit a híváskor a konkrét típus tölt ki -- így egyetlen függvény működik bármilyen típusra, típusbiztonság elvesztése nélkül.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
function first<T>(arr: T[]): T {
return arr[0];
}
const n = first([1, 2, 3]); // T = number
const s = first(['a', 'b', 'c']); // T = string
\end{lstlisting}
A TypeScript minden híváskor kikövetkezteti, mi legyen \texttt{T} -- a visszatérési érték típusa mindig pontosan illeszkedik a bemenethez.
\end{frame}
\begin{frame}[fragile]{Generikus interfészek}
\footnotesize
Nemcsak függvények, hanem interfészek (és típusok) is lehetnek generikusak -- ez az alapja például annak, ahogy a legtöbb adatbázis-eredmény vagy API válasz típus felépül.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: 'hello' };
interface ApiResponse<T> {
data: T;
success: boolean;
}
\end{lstlisting}
Egy \texttt{ApiResponse<User>} pontosan jelzi, hogy a \texttt{data} mező egy \texttt{User} objektumot fog tartalmazni -- ezt fogjuk a későbbi heteken az Express válaszainknál is használni.
\end{frame}
\begin{frame}[fragile]{Constraints -- a generikus típus korlátozása}
\footnotesize
Néha nem szeretnénk \emph{bármilyen} típust elfogadni -- csak olyanokat, amiknek van egy adott mezőjük vagy tulajdonságuk. Erre valók a \texttt{extends} kulcsszóval megadott \textbf{constraint}-ek.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength('hello'); // OK - a string-nek van length-je
logLength([1, 2, 3]); // OK - a tombnek is
logLength(42); // Hiba! a number-nek nincs length-je
\end{lstlisting}
\end{frame}
\begin{frame}{Összefoglalás -- Generikus típusok}
\footnotesize
\begin{block}{Generikus típusok}
\begin{itemize}
\item A generikák elkerülik a kódduplikációt anélkül, hogy fel kellene adnunk a típusbiztonságot
\item \texttt{function first<T>(arr: T[]): T} -- a típusparamétert a hívás tölti ki
\item Interfészek/típusok is lehetnek generikusak, pl. \texttt{Box<T>}, \texttt{ApiResponse<T>}
\item \texttt{T extends ...} -- a generikus típus korlátozása egy adott "alakra"
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,78 @@
\section{Utility Types}
\begin{frame}[fragile]{Miért kellenek a Utility Type-ok?}
\footnotesize
A TypeScript beépít néhány \textbf{beépített generikus segédtípust} (utility type), amikkel egy meglévő típusból egy \emph{módosított változatot} tudunk létrehozni -- anélkül, hogy a mezőket kézzel újra le kellene írnunk.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface User {
id: number;
name: string;
email: string;
}
\end{lstlisting}
A következő diákon ebből az egy \texttt{User} interfészből építünk különböző, gyakran szükséges változatokat -- pl. "frissítéshez", "listázáshoz" vagy "létrehozáshoz".
\end{frame}
\begin{frame}[fragile]{\texttt{Partial} és \texttt{Required}}
\footnotesize
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; }
type UserStrict = Required<User>;
// minden mezo kotelezo, meg ha eredetileg ? volt is
\end{lstlisting}
\begin{itemize}
\item \textbf{\texttt{Partial<T>}:} minden mezőt opcionálissá tesz -- tökéletes egy \texttt{PATCH} végpont bemenetéhez, ahol nem kötelező minden mezőt megadni
\item \textbf{\texttt{Required<T>}:} minden mezőt kötelezővé tesz -- a \texttt{Partial} ellentéte
\end{itemize}
\end{frame}
\begin{frame}[fragile]{\texttt{Pick} és \texttt{Omit}}
\footnotesize
Ez a két típus mezőket \emph{válogat ki}, vagy éppen \emph{hagy ki} egy meglévő típusból.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; }
\end{lstlisting}
\begin{block}{Gyakori felhasználás}
Egy adatbázisból visszakapott \texttt{User} rekordot gyakran \emph{nem} akarunk teljes egészében visszaküldeni az API válaszban (pl. egy jelszó-hash mezőt biztosan nem) -- a \texttt{Pick}/\texttt{Omit} pontosan erre való.
\end{block}
\end{frame}
\begin{frame}[fragile]{\texttt{Record} és \texttt{Readonly}}
\footnotesize
\begin{lstlisting}[language=JavaScript,style=NodeJS]
type UsersById = Record<number, User>;
// { [id: number]: User } - kulcs-ertek par tipusa
type ImmutableUser = Readonly<User>;
// minden mezo csak olvashato (readonly)
\end{lstlisting}
\begin{itemize}
\item \textbf{\texttt{Record<K, V>}:} egy objektum típusa, ahol a kulcsok \texttt{K}, az értékek \texttt{V} típusúak -- gyakori egy azonosító szerint indexelt "gyorsítótárnál"
\item \textbf{\texttt{Readonly<T>}:} minden mezőt \texttt{readonly}-vá tesz -- hasznos, ha biztosítani akarjuk, hogy egy objektum ne módosuljon
\end{itemize}
\end{frame}
\begin{frame}{Összefoglalás -- Utility Types}
\footnotesize
\begin{block}{Utility Types}
\begin{itemize}
\item \texttt{Partial<T>} / \texttt{Required<T>} -- mezők opcionálissá / kötelezővé tétele
\item \texttt{Pick<T, K>} / \texttt{Omit<T, K>} -- mezők kiválogatása / kihagyása
\item \texttt{Record<K, V>} -- kulcs-érték típusú objektum
\item \texttt{Readonly<T>} -- minden mező írásvédetté tétele
\item Mindegyik egy meglévő típusból épít egy \emph{módosított változatot} -- kódismétlés nélkül
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,122 @@
\section{Enum/Osztályok}
\begin{frame}[fragile]{Enum alapok}
\footnotesize
Az \texttt{enum} (felsorolt típus) egy névvel ellátott, korlátozott értékkészletet definiál -- hasonló célt szolgál, mint a korábban látott string literal union, de saját névtérrel és futásidejű objektummal is jár.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
enum Role {
Admin, // 0
Editor, // 1
Viewer, // 2
}
enum Status {
Pending = 'pending',
Active = 'active',
Closed = 'closed',
}
const myRole: Role = Role.Admin;
\end{lstlisting}
Numerikus enumnál automatikusan 0-tól számozódnak az értékek; string enumnál mi adjuk meg a konkrét szöveges értéket -- ez utóbbi jobban olvasható naplókban, hibaüzenetekben.
\end{frame}
\begin{frame}[fragile]{Osztályok TypeScript-ben}
\footnotesize
A TypeScript osztályai a JavaScript ES6 osztályaira épülnek, kiegészítve a mezők típusozásával.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Szia, ${this.name} vagyok!`;
}
}
const p = new Person('Anna', 25);
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]{Access modifierek}
\footnotesize
Az osztály mezőihez és metódusaihoz \textbf{láthatósági szinteket} rendelhetünk -- ez segít elrejteni az implementációs részleteket, és csak a szándékolt "publikus felületet" mutatni kifelé.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
class BankAccount {
private balance: number = 0;
public readonly owner: string;
constructor(owner: string) {
this.owner = owner;
}
public deposit(amount: number): void {
this.balance += amount;
}
}
\end{lstlisting}
\begin{itemize}
\item \textbf{\texttt{public}} (alapértelmezett): bárhonnan elérhető
\item \textbf{\texttt{private}}: csak az osztályon belülről érhető el
\item \textbf{\texttt{protected}}: az osztályon és leszármazottjain belül érhető el
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Interfész implementálása osztállyal}
\footnotesize
Egy osztály az \texttt{implements} kulcsszóval kötelezettséget vállal arra, hogy megfelel egy adott interfésznek -- a fordító ellenőrzi, hogy tényleg minden szükséges mezőt/metódust megvalósít-e.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
interface Greetable {
greet(): string;
}
class Person implements Greetable {
constructor(private name: string) {}
greet(): string {
return `Szia, ${this.name} vagyok!`;
}
}
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]{Decorators -- rövid kitekintés}
\footnotesize
A \textbf{decorator} egy speciális jelölés (\texttt{@valami}), amit osztályok, mezők vagy metódusok elé írhatunk, és ami futásidejű viselkedést csatol hozzájuk -- kicsit hasonlóan ahhoz, ahogy Express-ben a middleware-ek "csomagolják be" a route handlereket.
\begin{block}{Miért fontos ez most csak kitekintésként?}
A decorators ma még kísérleti funkció TypeScript-ben, de rendkívül elterjedt az ORM-ekben (pl. a későbbi heteken megismert TypeORM) -- ott \texttt{@Entity}, \texttt{@Column} jelölésekkel írjuk le az adatbázis-táblákat.
\end{block}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
@Entity()
class User {
@Column()
name: string;
}
\end{lstlisting}
\end{frame}
\begin{frame}{Összefoglalás -- Enum és Osztályok}
\footnotesize
\begin{block}{Enum és Osztályok}
\begin{itemize}
\item \texttt{enum}: névvel ellátott, korlátozott értékkészlet (numerikus vagy string alapú)
\item Osztályok: mezők típusozása, konstruktor, metódusok -- az ES6 osztályokra épül
\item Access modifierek: \texttt{public} / \texttt{private} / \texttt{protected} -- a láthatóság szabályozása
\item \texttt{implements}: egy osztály kötelezettséget vállal egy interfész megvalósítására
\item Decorators: \texttt{@valami} jelölések, amik későbbi heteken (TypeORM) kerülnek elő
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,32 @@
\section{Összefoglalás}
\begin{frame}{Teljes összefoglalás}
\footnotesize
\begin{block}{Alapok}
Statikus típusrendszer JavaScript fölött; \texttt{tsc} fordít, \texttt{tsx}/\texttt{ts-node} közvetlenül futtat; \texttt{tsconfig.json} vezérli a fordítást (\texttt{strict} ajánlott).
\end{block}
\begin{block}{Típusok}
Primitívek, tömbök, tuple-ök; \texttt{any} kerülendő, \texttt{unknown} biztonságos; \texttt{interface}/\texttt{type}; union (\texttt{|}) és intersection (\texttt{\&}); generikák (\texttt{<T>}); utility types (\texttt{Partial}, \texttt{Pick}, stb.)
\end{block}
\begin{block}{Osztályok}
Access modifierek (\texttt{public}/\texttt{private}/\texttt{protected}), \texttt{implements}, \texttt{enum}, decorators (kitekintés a TypeORM felé)
\end{block}
\end{frame}
\begin{frame}{Hogyan tovább?}
\footnotesize
A legjobb módja a TypeScript elsajátításának a \emph{gyakorlás} -- az elmélet önmagában nem elég, próbáld ki a saját kódodban is.
\begin{itemize}
\item Alakítsd át egy régebbi, sima JavaScript projektedet fokozatosan TypeScript-re, fájlonként haladva
\item Kapcsold be a \texttt{strict} módot a \texttt{tsconfig.json}-ban -- kezdetben tűnhet szigorúnak, de hosszabb távon ez fizetődik ki igazán
\item Olvasd a hivatalos dokumentációt: \texttt{typescriptlang.org/docs} -- naprakész, sok interaktív példával
\item A tárgy további heteiben (Express.js + TypeScript, majd TypeORM) a itt tanultak folyamatosan elő fognak kerülni
\end{itemize}
\begin{alertblock}{Emlékeztető}
Ez az anyag kiegészítő, önálló tanulást szolgál -- nem számonkérendő, de a mélyebb megértés a féléves munkádat is megkönnyíti.
\end{alertblock}
\end{frame}
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
\documentclass[aspectratio=169]{beamer}
\usepackage{../common/webfejl}
\title{TypeScript}
\subtitle{Kiegészítő tananyag -- a JavaScript alapoktól a haladó típusokig}
\begin{document}
\begin{frame}
\titlepage
\logoalul
\end{frame}
\input{sections/00-bevezetes}
\input{sections/01-alaptipusok}
\input{sections/02-interface-type}
\input{sections/03-fuggvenyek}
\input{sections/04-union-intersection}
\input{sections/05-generics}
\input{sections/06-utility-types}
\input{sections/07-enums-classes}
\input{sections/08-osszefoglalas}
\end{document}
Binary file not shown.
@@ -37,8 +37,8 @@ Az első hat hét az alapozásról szól: megismerjük a HTTP protokollt, felép
\begin{block}{} \begin{block}{}
\begin{tabular}{ll} \begin{tabular}{ll}
\textbf{1. hét} & Alapok, Node.js, HTTPS, NPM package manager \\ \textbf{1. hét} & Alapok, Node.js, HTTPS, NPM package manager \\
\textbf{2. hét} & REST API, architekturális felépítések, CQRS \\ \textbf{2. hét} & Express.js, Routing, Postman, TypeScript \\
\textbf{3. hét} & Express.js, Routing, Postman, TypeScript \\ \textbf{3. hét} & REST API, architekturális felépítések, CQRS \\
\textbf{4. hét} & ORM, database connection, CRUD operations \\ \textbf{4. hét} & ORM, database connection, CRUD operations \\
\textbf{5. hét} & TypeORM, Aggregates, Repositories \\ \textbf{5. hét} & TypeORM, Aggregates, Repositories \\
\textbf{6. hét} & \textbf{ZH 1}: Database CRUD, ORM, CQRS + DTOs, Mappers \\ \textbf{6. hét} & \textbf{ZH 1}: Database CRUD, ORM, CQRS + DTOs, Mappers \\
@@ -8,6 +8,6 @@ A mai órán megismertük a webes kommunikáció alapjait, elindítottunk egy No
\item \textbf{Fejlesztői környezet telepítése} (Node.js, npm) -- ha még nincs telepítve, mindenképp pótold otthon \item \textbf{Fejlesztői környezet telepítése} (Node.js, npm) -- ha még nincs telepítve, mindenképp pótold otthon
\item \textbf{Git és verziókezelés alapjai} -- a kódunkat innentől mindig verziókezelve fogjuk fejleszteni \item \textbf{Git és verziókezelés alapjai} -- a kódunkat innentől mindig verziókezelve fogjuk fejleszteni
\item \textbf{Első Express.js alkalmazás elkészítése} -- a jövő héten a beépített \texttt{http} modul helyett egy sokkal kényelmesebb keretrendszert használunk \item \textbf{Első Express.js alkalmazás elkészítése} -- a jövő héten a beépített \texttt{http} modul helyett egy sokkal kényelmesebb keretrendszert használunk
\item \textbf{REST API alapok megismerése} (2. hét) -- ráépítünk a mai HTTP-s tudásra \item \textbf{REST API alapok megismerése} (3. hét) -- miután megírtuk az első route-jainkat, rátérünk a tudatos tervezésükre is
\end{enumerate} \end{enumerate}
\end{frame} \end{frame}
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
\documentclass[aspectratio=169]{beamer}
\usepackage{../common/webfejl}
\title{Webtechnológia és webalkalmazás-fejlesztés}
\subtitle{2. hét -- Express.js, Routing, Postman, TypeScript}
\begin{document}
\begin{frame}
\titlepage
\logoalul
\end{frame}
\input{sections/00-visszatekintes}
\input{sections/01-expressjs}
\input{sections/02-routing}
\input{sections/03-postman}
\input{sections/04-typescript}
\input{sections/05-kovetkezo-lepesek}
\end{document}
@@ -2,30 +2,31 @@
\begin{frame}{Miről volt szó múlt héten?} \begin{frame}{Miről volt szó múlt héten?}
\footnotesize \footnotesize
Az első héten megismertük a webes kommunikáció alapköveit: a HTTP protokollt, a Node.js futtatókörnyezetet és az npm csomagkezelőt. Ezek nélkül nem tudnánk elindulni -- de önmagukban még nem elegendők egy jól használható backend rendszer megépítéséhez. Az első héten megismertük a webes kommunikáció alapjait: a HTTP protokollt, a Node.js futtatókörnyezetet, és elindítottunk egy egyszerű szervert a beépített \texttt{http} modullal. Emellett megtanultuk, hogyan kezeljük npm-mel a projekt függőségeit.
\begin{itemize} \begin{itemize}
\item \textbf{HTTP/HTTPS:} Request-Response modell, állapotmentes protokoll, metódusok (GET, POST, PUT, PATCH, DELETE), státuszkódok \item \textbf{HTTP:} Request-Response modell, metódusok (GET, POST, PUT, PATCH, DELETE), státuszkódok
\item \textbf{Node.js:} szerveroldali JavaScript futtatókörnyezet, beépített \texttt{http} modul egy egyszerű szerver elindítására \item \textbf{Node.js:} \texttt{http.createServer} -- egy függvény, ami minden kérést kiszolgál
\item \textbf{npm:} csomagkezelés, \texttt{package.json}, függőségek \item \textbf{npm:} \texttt{package.json}, csomagok telepítése, \texttt{npm scripts}
\end{itemize} \end{itemize}
\begin{block}{A hiányzó darab} \begin{block}{A hiányzó darab}
Múlt héten a \texttt{http.createServer}-rel csak egyetlen, mindent kiszolgáló függvényt írtunk. Éles alkalmazásoknál ez gyorsan kezelhetetlenné válik -- szükségünk van egy \emph{tervezési logikára}, ami megmondja, hogyan szervezzük az útvonalainkat, adatainkat és a kódunk rétegeit. A nyers \texttt{http} modullal minden egyes útvonalat, minden HTTP metódust és a kérés törzsének feldolgozását is kézzel, \texttt{if-else} ágakkal kellene megírnunk. Ez pár végpontnál még kezelhető, de gyorsan átláthatatlanná válik.
\end{block} \end{block}
\end{frame} \end{frame}
\begin{frame}{Mi a célja a mai órának?} \begin{frame}{Mi a célja a mai órának?}
\footnotesize \footnotesize
A mai anyag három, egymásra épülő témát dolgoz fel: hogyan tervezzünk jól használható API-kat, hogyan szervezzük a backend kódunkat rétegekbe, és hogyan válasszuk szét az írási és olvasási műveleteket, ha a rendszerünk ezt megkívánja. A mai órán egy valódi keretrendszert vezetünk be, amivel a múlt heti kézi munkát nagyrészt kiváltjuk, majd megtanuljuk, hogyan teszteljük és hogyan típusozzuk a kódunkat.
\begin{itemize} \begin{itemize}
\item \textbf{REST API alapok} -- hogyan tervezzünk következetes, könnyen használható API-t \item \textbf{Express.js} -- a legelterjedtebb Node.js webes keretrendszer, ami elveszi a routing és a kérésfeldolgozás nagy részének terhét
\item \textbf{Architekturális felépítések} -- hogyan szervezzük a backend kódunkat rétegekbe, hogy karbantartható maradjon \item \textbf{Routing} -- hogyan szervezzük az útvonalainkat, hogyan olvassuk ki a kérés adatait
\item \textbf{CQRS} -- mikor éri meg szétválasztani az írási és olvasási műveleteket \item \textbf{Postman} -- hogyan teszteljük a saját API-nkat anélkül, hogy frontendet kellene hozzá írnunk
\item \textbf{TypeScript} -- hogyan adjunk típusbiztonságot a JavaScript kódunkhoz
\end{itemize} \end{itemize}
\begin{alertblock}{Fontos} \begin{alertblock}{Fontos}
A mai témák még mindig \emph{tervezési} kérdések -- konkrét kódot Express.js-ben csak a jövő héttől írunk. Most a "miért így?" kérdésre keressük a választ, hogy a következő heti kód már értelmes döntéseken alapuljon. Ma még mindig egyszerű, tárolás nélküli (memóriában tartott) példákkal dolgozunk -- az adatbázis-kapcsolat csak a 4. héten kerül képbe. Most a keretrendszerre és az eszközökre fókuszálunk.
\end{alertblock} \end{alertblock}
\end{frame} \end{frame}
@@ -0,0 +1,95 @@
\section{Express.js}
\begin{frame}[fragile]{Miért nem elég a nyers \texttt{http} modul?}
\footnotesize
Emlékezzünk vissza az 1. héten írt szerverre: egyetlen callback-ben kellett eldöntenünk, milyen útvonalra és milyen metódussal érkezett a kérés.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const server = http.createServer((req, res) => {
if (req.url === '/users' && req.method === 'GET') {
// ...
} else if (req.url === '/users' && req.method === 'POST') {
// ...
} else if (req.url.startsWith('/users/') && req.method === 'GET') {
// egy adott felhasznalo -- id kezzel kivagva az URL-bol
} // es igy tovabb...
});
\end{lstlisting}
\begin{alertblock}{A probléma}
Tíz-húsz végpontnál ez a lánc kezelhetetlenné válik: nincs beépített segítség a paraméterek kiolvasásához, a JSON törzs feldolgozásához, vagy az útvonalak csoportosításához.
\end{alertblock}
\end{frame}
\begin{frame}{Mi az Express.js?}
\footnotesize
Az \textbf{Express.js} a legelterjedtebb Node.js webes keretrendszer (web framework): a nyers \texttt{http} modulra épül, és eltünteti a kézi \texttt{if-else} láncokat egy sokkal kényelmesebb, deklaratív routing API-val.
\begin{itemize}
\item Egyszerű, minimalista -- nem kényszerít ránk egy előírt projektszerkezetet
\item Beépített segítség: útvonal-paraméterek, query stringek, JSON válaszok
\item Hatalmas ökoszisztéma: middleware-ek szinte minden problémára (autentikáció, naplózás, CORS, stb.)
\item Ez lesz a tárgy hátralévő részének elsődleges keretrendszere
\end{itemize}
\begin{block}{Fontos}
Az Express \emph{nem} helyettesíti a Node.js-t -- ráépül. Amit az 1. héten a \texttt{http} modulról tanultunk (request/response ciklus), az Express alatt is pontosan úgy működik.
\end{block}
\end{frame}
\begin{frame}[fragile]{Az első Express alkalmazás}
\footnotesize
Néhány sorból már működő szerverünk van -- hasonlítsd össze az előző dia \texttt{if-else} láncával!
\begin{block}{Telepítés}
\begin{verbatim}
npm install express
\end{verbatim}
\end{block}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.listen(3000, () => {
console.log('Szerver fut: http://localhost:3000');
});
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]{A middleware fogalma}
\footnotesize
Az Express legfontosabb koncepciója a \textbf{middleware}: egy függvény, ami a kérés és a válasz között fut le, és vagy továbbengedi a folyamatot (\texttt{next()}), vagy lezárja azt egy válasszal.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // tovabb a kovetkezo middleware-hez/route-hoz
});
app.use(express.json()); // JSON body-t olvaso middleware
\end{lstlisting}
\begin{itemize}
\item A kérések egy "csövön" (pipeline) haladnak végig, middleware-eken keresztül
\item \texttt{express.json()} nélkül a \texttt{req.body} üres lenne POST/PUT kéréseknél
\item Naplózás, autentikáció, hibakezelés -- mind middleware-ként valósul meg
\end{itemize}
\end{frame}
\begin{frame}{Összefoglalás -- Express.js}
\footnotesize
\begin{block}{Express.js alapok}
\begin{itemize}
\item Node.js-re épülő, minimalista webes keretrendszer
\item Kiváltja a kézi \texttt{if-else} útvonal-elágazásokat
\item \texttt{app.listen()} -- szerver indítása, ugyanaz mint a nyers \texttt{http}-nél
\item Middleware: \texttt{app.use()} -- a kérés-válasz "csövének" építőeleme
\item \texttt{express.json()} -- beépített middleware a JSON törzs feldolgozásához
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,125 @@
\section{Routing}
\begin{frame}[fragile]{HTTP metódusok Express-ben}
\footnotesize
Minden HTTP metódushoz külön Express függvény tartozik, ami pontosan a metódus nevét viseli -- ez sokkal olvashatóbb, mint egy \texttt{if (req.method === ...)} lánc.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.get('/users', (req, res) => { /* lista */ });
app.post('/users', (req, res) => { /* letrehozas */ });
app.get('/users/:id', (req, res) => { /* egy elem */ });
app.put('/users/:id', (req, res) => { /* teljes frissites */ });
app.patch('/users/:id', (req, res) => { /* reszleges frissites */ });
app.delete('/users/:id', (req, res) => { /* torles */ });
\end{lstlisting}
\begin{block}{Emlékeztető}
Ez pontosan a 2. héten (a jövő héten, amikor visszatérünk rá) tárgyalt CRUD $\to$ HTTP metódus táblázatot követi -- nem véletlen az egyezés.
\end{block}
\end{frame}
\begin{frame}[fragile]{Route paraméterek és query stringek}
\footnotesize
Az útvonalban lévő \texttt{:id}-hez hasonló szegmenseket \textbf{route paramétereknek} hívjuk, a kérdőjel utáni \texttt{?key=value} részt pedig \textbf{query stringnek}. Express mindkettőt készen, feldolgozva adja át.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
// GET /users/123?includeOrders=true
app.get('/users/:id', (req, res) => {
const userId = req.params.id; // "123"
const includeOrders = req.query.includeOrders; // "true"
res.json({ id: userId, includeOrders });
});
\end{lstlisting}
\begin{itemize}
\item \textbf{\texttt{req.params}:} az útvonal része, egy konkrét erőforrás azonosítására (\texttt{/users/:id})
\item \textbf{\texttt{req.query}:} opcionális finomítás (lapozás, szűrés, rendezés)
\end{itemize}
\end{frame}
\begin{frame}[fragile]{A kérés törzse: \texttt{req.body}}
\footnotesize
\texttt{POST}, \texttt{PUT} és \texttt{PATCH} kéréseknél az adat jellemzően a kérés törzsében (body) érkezik JSON formátumban -- ehhez szükséges az előző órán megismert \texttt{express.json()} middleware.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.use(express.json());
app.post('/users', (req, res) => {
const { name, email } = req.body;
const newUser = { id: Date.now(), name, email };
users.push(newUser);
res.status(201).json(newUser);
});
\end{lstlisting}
\begin{block}{Emlékeztető az 1. hétről}
A \texttt{201 Created} a helyes státuszkód sikeres \texttt{POST} esetén -- ezt már az 1. héten megismertük.
\end{block}
\end{frame}
\begin{frame}[fragile]{Útvonalak szervezése: \texttt{express.Router()}}
\footnotesize
Ahogy nő az alkalmazás, egyetlen fájlban tartani az összes route-ot áttekinthetetlenné válik. Az \texttt{express.Router()} lehetővé teszi, hogy erőforrásonként külön fájlba szervezzük az útvonalakat.
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{routes/users.js}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const router = express.Router();
router.get('/', listUsers);
router.post('/', createUser);
module.exports = router;
\end{lstlisting}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{app.js}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const usersRouter =
require('./routes/users');
app.use(
'/users',
usersRouter
);
\end{lstlisting}
\end{block}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile]{Hibakezelés alapjai}
\footnotesize
Express két speciális esetet kezel middleware-ekkel: amikor egy útvonal nem létezik, és amikor egy route handler hibát dob.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
// 404 - ha semelyik route nem illeszkedett
app.use((req, res) => {
res.status(404).json({ error: 'Nem talalhato' });
});
// hibakezelo middleware -- negy parameter jelzi Expressnek!
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Szerver hiba' });
});
\end{lstlisting}
\begin{block}{Fontos}
A hibakezelő middleware-t \emph{a legvégén} kell regisztrálni, az összes route után -- Express a négy paraméter alapján ismeri fel.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Routing}
\footnotesize
\begin{block}{Routing Express-ben}
\begin{itemize}
\item \texttt{app.get/post/put/patch/delete} -- metódusonként külön, olvasható route definíció
\item \texttt{req.params} -- útvonal paraméterek, \texttt{req.query} -- query string, \texttt{req.body} -- kérés törzse
\item \texttt{express.Router()} -- útvonalak szervezése külön fájlokba, erőforrásonként
\item 404 és hibakezelő middleware a route-ok végén
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,67 @@
\section{Postman}
\begin{frame}{Mi a Postman, és miért használjuk?}
\footnotesize
Eddig minden végpontunkat vagy böngészőből (csak GET-hez alkalmas), vagy egyáltalán nem próbáltuk ki. A \textbf{Postman} egy grafikus alkalmazás, amivel bármilyen HTTP kérést összeállíthatunk és elküldhetünk -- anélkül, hogy előbb frontendet kellene írnunk hozzá.
\begin{itemize}
\item Bármilyen metódus (GET, POST, PUT, PATCH, DELETE) kipróbálható
\item Egyedi fejlécek (headers) és JSON törzs (body) is összeállítható
\item A választ (státuszkód, headerek, body) azonnal, formázva látjuk
\item Elmenthető, újra futtatható, csapattagokkal megosztható kéréssorozatok
\end{itemize}
\begin{block}{Miért nem elég a böngésző?}
A böngésző címsorába írt URL mindig \texttt{GET} kérést küld -- \texttt{POST}, \texttt{PUT}, \texttt{DELETE} kéréshez, vagy egyedi fejlécekhez (pl. \texttt{Authorization}) külön eszköz kell.
\end{block}
\end{frame}
\begin{frame}[fragile]{Egy kérés összeállítása}
\footnotesize
Egy Postman-kérés pontosan azokból az elemekből áll, amiket az 1. héten a HTTP kérés felépítéseként már megismertünk: metódus, URL, fejlécek, törzs.
\begin{block}{Amit beállítunk}
\begin{itemize}
\item \textbf{Metódus:} pl. \texttt{POST}
\item \textbf{URL:} \texttt{http://localhost:3000/users}
\item \textbf{Headers:} \texttt{Content-Type: application/json}
\item \textbf{Body (raw, JSON):}
\end{itemize}
\end{block}
\begin{verbatim}
{
"name": "Kis Anna",
"email": "anna@example.com"
}
\end{verbatim}
Küldés után azonnal látjuk a választ: \texttt{201 Created} és a visszakapott, azonosítóval kiegészített felhasználó.
\end{frame}
\begin{frame}{Collections és Environment változók}
\footnotesize
Ahogy egyre több végpontot tesztelünk, hasznos ezeket rendszerezni, és elkerülni, hogy mindig kézzel írjuk be az alap URL-t.
\begin{itemize}
\item \textbf{Collection:} kérések egy mappába rendezett gyűjteménye -- pl. minden "Users" végpont egy collection-ben
\item \textbf{Environment:} névhez kötött változók (pl. \texttt{baseUrl}), amikkel a kéréseinkben \texttt{\{\{baseUrl\}\}/users} írható a teljes URL helyett
\item Így egyetlen változó átírásával válthatunk fejlesztői és éles környezet között
\end{itemize}
\begin{block}{Gyakorlati tipp}
Fejlesztés közben minden újonnan megírt végpontot azonnal próbálj ki Postmanben -- ez sokkal gyorsabb visszajelzést ad, mint egy teljes frontend felület megvárása.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Postman}
\footnotesize
\begin{block}{Postman}
\begin{itemize}
\item Grafikus eszköz tetszőleges HTTP kérés összeállítására és elküldésére
\item Kiválthatja a böngészőt olyan esetekben, amikor nem elég a sima GET
\item Kérés = metódus + URL + headers + body -- ugyanaz, mint amit HTTP-ből már tudunk
\item Collections: kérések rendszerezése; Environments: újrafelhasználható változók
\end{itemize}
\end{block}
\end{frame}
@@ -1,17 +0,0 @@
\section{Következő lépések}
\begin{frame}{Következő lépések}
\footnotesize
Ma megismertük, hogyan tervezzünk jól használható REST API-t, hogyan szervezzük a backend kódunkat rétegekbe, és mikor érdemes szétválasztani az írási és olvasási műveleteket. A jövő héten ezekre a tervezési elvekre már konkrét kódot építünk.
\begin{enumerate}
\item \textbf{Express.js megismerése} -- a jövő heti keretrendszer, amivel az API/Controller réteget ténylegesen megírjuk
\item \textbf{Routing gyakorlása} -- a mai órán tervezett erőforrás-orientált URL-eket Express.js route handlerekként fogjuk implementálni
\item \textbf{Postman telepítése} -- API-k manuális teszteléséhez, hogy a saját végpontjainkat is ki tudjuk próbálni
\item \textbf{TypeScript alapok} -- a jövő héttől fokozatosan áttérünk típusos JavaScriptre
\end{enumerate}
\begin{alertblock}{Javaslat}
Nézd át még egyszer a CRUD $\to$ HTTP metódus táblázatot -- a jövő héten ezt fogjuk közvetlenül route definíciókra fordítani.
\end{alertblock}
\end{frame}
@@ -0,0 +1,75 @@
\section{TypeScript}
\begin{frame}{Miért TypeScript?}
\footnotesize
A JavaScript \textbf{gyengén típusos} nyelv: egy változó bármikor bármilyen típusú értéket felvehet, és a hibák jó része csak futás közben derül ki -- gyakran éppen egy éles felhasználónál. A \textbf{TypeScript} a JavaScript egy kiterjesztése, ami \emph{statikus típusokat} ad hozzá.
\begin{itemize}
\item A típushibák nagy része már fordításkor (kódírás közben) kiderül, nem futás közben
\item Az IDE sokkal pontosabb kódkiegészítést és hibajelzést tud adni
\item Nagyobb csapatban, nagyobb kódbázisban különösen megtérül
\item A TypeScript kód JavaScript-té fordul le -- a böngésző/Node.js sosem futtat TS-t közvetlenül
\end{itemize}
\begin{block}{Fontos}
A TypeScript \emph{opcionális} réteg a JavaScript fölött -- minden érvényes JavaScript kód (majdnem) érvényes TypeScript is, fokozatosan vezethető be egy meglévő projektbe.
\end{block}
\end{frame}
\begin{frame}[fragile]{Telepítés és futtatás}
\footnotesize
A TypeScript fájlok (\texttt{.ts} kiterjesztéssel) nem futtathatók közvetlenül Node.js-szel -- előbb JavaScript-té kell őket fordítani, vagy egy erre képes futtatót kell használnunk.
\begin{block}{Fordítás \texttt{tsc}-vel}
\begin{verbatim}
npm install -D typescript
npx tsc --init # letrehozza a tsconfig.json-t
npx tsc # lefordit minden .ts fajlt .js-re
\end{verbatim}
\end{block}
\begin{block}{Fejlesztés közben: \texttt{tsx}}
\begin{verbatim}
npx tsx index.ts
\end{verbatim}
A \texttt{tsx} fordítás nélkül, közvetlenül futtatja a TypeScript fájlt -- fejlesztés közben ez a kényelmesebb.
\end{block}
\end{frame}
\begin{frame}[fragile]{Alaptípusok és egy egyszerű interfész}
\footnotesize
A típusokat a változó/paraméter neve után, kettősponttal adjuk meg. Több mező közös leírásához pedig egy \texttt{interface}-t érdemes definiálni.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
let name: string = 'Anna';
let age: number = 25;
let isActive: boolean = true;
let tags: string[] = ['admin', 'user'];
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
\end{lstlisting}
\end{frame}
\begin{frame}{Összefoglalás -- TypeScript}
\footnotesize
\begin{block}{TypeScript alapok}
\begin{itemize}
\item A JavaScript statikus típusokkal kiegészített változata -- fordításkor szűri a típushibákat
\item \texttt{tsc} -- fordítás JavaScript-té; \texttt{tsx}/\texttt{ts-node} -- közvetlen futtatás fejlesztéskor
\item Alaptípusok: \texttt{string}, \texttt{number}, \texttt{boolean}, tömbök
\item \texttt{interface} -- több mezőből álló adatszerkezet típusának leírása
\end{itemize}
\end{block}
\begin{alertblock}{Több is van!}
Ez csak az alapok -- a generikus típusokról, a \texttt{union}/\texttt{intersection} típusokról és más haladó fogalmakról egy \emph{külön, önállóan is végigkövethető} kiegészítő anyagban olvashatsz, ha mélyebben el szeretnél merülni.
\end{alertblock}
\end{frame}
@@ -0,0 +1,16 @@
\section{Következő lépések}
\begin{frame}{Következő lépések}
\footnotesize
Ma megírtuk az első valódi Express.js alkalmazásunkat, megtanultuk, hogyan teszteljük Postmannel, és belekóstoltunk a TypeScriptbe. Eddig azonban inkább \emph{gyakorlati megérzés} alapján dolgoztunk -- a jövő héten ezt tudatos tervezéssé alakítjuk.
\begin{enumerate}
\item \textbf{REST API tervezési elvek} -- visszatekintünk a ma írt útvonalainkra: jól neveztük-e el őket?
\item \textbf{Architekturális felépítések} -- hogyan válasszuk szét a route handlert (amit ma írtunk) az üzleti logikától
\item \textbf{CQRS} -- mikor éri meg külön kezelni az írási és olvasási műveleteket
\end{enumerate}
\begin{alertblock}{Javaslat}
Nézd át még egyszer a ma írt route-jaidat -- a jövő héten pontosan ezekre a konkrét példákra fogunk tervezési elveket alkalmazni.
\end{alertblock}
\end{frame}
@@ -2,7 +2,7 @@
\usepackage{../common/webfejl} \usepackage{../common/webfejl}
\title{Webtechnológia és webalkalmazás-fejlesztés} \title{Webtechnológia és webalkalmazás-fejlesztés}
\subtitle{2. hét -- REST API, Architekturális felépítések, CQRS} \subtitle{3. hét -- REST API, Architekturális felépítések, CQRS}
\begin{document} \begin{document}
@@ -0,0 +1,32 @@
\section{Visszatekintés}
\begin{frame}{Miről volt szó múlt héten?}
\footnotesize
Az első héten megismertük a webes kommunikáció alapköveit (HTTP, Node.js, npm), a másodikon pedig ezekre építve megírtuk az első valódi backend kódunkat: Express.js-szel útvonalakat (route-okat) hoztunk létre, Postmannel kipróbáltuk őket, és belekóstoltunk a TypeScriptbe is.
\begin{itemize}
\item \textbf{Express.js:} \texttt{app.get/post/put/delete} route-ok, middleware-ek, \texttt{express.Router()}
\item \textbf{Routing:} \texttt{req.params}, \texttt{req.query}, \texttt{req.body} kezelése, JSON válaszok
\item \textbf{Postman:} kérések összeállítása és tesztelése kód írása nélkül
\item \textbf{TypeScript:} alaptípusok, egy egyszerű interfész, fordítás \texttt{tsc}-vel
\end{itemize}
\begin{block}{A hiányzó darab}
Múlt héten \emph{gyakorlati megérzés} alapján neveztük el az útvonalainkat és szerveztük a kódunkat. Ez működött, de nem feltétlenül a legjobb, legkövetkezetesebb módon -- ma megnézzük, milyen \emph{tervezési elvek} állnak a jó API-k és a jó backend-architektúrák mögött.
\end{block}
\end{frame}
\begin{frame}{Mi a célja a mai órának?}
\footnotesize
A mai anyag három, egymásra épülő témát dolgoz fel: hogyan tervezzünk jól használható API-kat, hogyan szervezzük a backend kódunkat rétegekbe, és hogyan válasszuk szét az írási és olvasási műveleteket, ha a rendszerünk ezt megkívánja.
\begin{itemize}
\item \textbf{REST API alapok} -- hogyan tervezzünk következetes, könnyen használható API-t
\item \textbf{Architekturális felépítések} -- hogyan szervezzük a backend kódunkat rétegekbe, hogy karbantartható maradjon
\item \textbf{CQRS} -- mikor éri meg szétválasztani az írási és olvasási műveleteket
\end{itemize}
\begin{alertblock}{Fontos}
A múlt héten megírt Express.js route-jaink jó kiindulópontok, de a mai órán \emph{visszamenőleg is} átgondoljuk őket: melyik URL-t neveznénk most másképp, hol hiányzik egy réteg, hol keverednek a felelősségek. A "miért így?" kérdésre keressük a választ, hogy a jövőben már tudatosan jól tervezett kódot írjunk.
\end{alertblock}
\end{frame}
@@ -18,7 +18,7 @@ A rétegzett felépítésnél a kódot vízszintes rétegekbe szervezzük, ahol
\begin{block}{} \begin{block}{}
\begin{itemize} \begin{itemize}
\item \textbf{API / Controller:} fogadja a HTTP kéréseket, validálja a bemenetet, meghívja az Application réteget, visszaadja a választ -- ezt Express.js-ben írjuk meg a jövő héten \item \textbf{API / Controller:} fogadja a HTTP kéréseket, validálja a bemenetet, meghívja az Application réteget, visszaadja a választ -- ezt Express.js-ben már megírtuk a múlt héten
\item \textbf{Application:} az egyes use case-eket (parancsokat/lekérdezéseket) orchesztrálja -- ide kerülnek majd a CQRS command/query handlerek \item \textbf{Application:} az egyes use case-eket (parancsokat/lekérdezéseket) orchesztrálja -- ide kerülnek majd a CQRS command/query handlerek
\item \textbf{Domain:} a tiszta üzleti logika, entitások, aggregates -- semmilyen technikai függősége nincs, nem "tudja", hogy Express-ben vagy adatbázissal fut \item \textbf{Domain:} a tiszta üzleti logika, entitások, aggregates -- semmilyen technikai függősége nincs, nem "tudja", hogy Express-ben vagy adatbázissal fut
\end{itemize} \end{itemize}
@@ -52,7 +52,7 @@ Az MVC (Model-View-Controller) a rétegzett architektúra egyik legismertebb kon
\end{itemize} \end{itemize}
\begin{block}{Backend kontextusban} \begin{block}{Backend kontextusban}
Amikor a jövő héten Express.js-ben "route handler"-t írunk, az gyakorlatilag az API/Controller réteg szerepét tölti be: fogadja a HTTP kérést, és delegálja a munkát az Application rétegnek. Amikor a múlt héten Express.js-ben "route handler"-t írtunk, az gyakorlatilag az API/Controller réteg szerepét töltötte be: fogadja a HTTP kérést, és delegálja a munkát az Application rétegnek.
\end{block} \end{block}
\end{frame} \end{frame}
@@ -0,0 +1,16 @@
\section{Következő lépések}
\begin{frame}{Következő lépések}
\footnotesize
Ma megismertük, hogyan tervezzünk jól használható REST API-t, hogyan szervezzük a backend kódunkat rétegekbe, és mikor érdemes szétválasztani az írási és olvasási műveleteket. Eddig azonban minden adatunk csak a memóriában élt -- a jövő héten ezen változtatunk.
\begin{enumerate}
\item \textbf{ORM megismerése} -- egy eszköz, ami a Domain rétegünk objektumait az adatbázis tábláira képezi le, SQL írása nélkül
\item \textbf{Adatbázis-kapcsolat kiépítése} -- az Infrastructure réteg végre ténylegesen egy adatbázishoz kapcsolódik
\item \textbf{CRUD műveletek} -- a ma megtervezett \texttt{POST}/\texttt{GET}/\texttt{PUT}/\texttt{DELETE} végpontok mögé valódi, perzisztens adatkezelést építünk
\end{enumerate}
\begin{alertblock}{Javaslat}
Gondold át még egyszer az öt rétegű architektúrát -- a jövő héten pontosan az Infrastructure réteget töltjük meg valódi tartalommal.
\end{alertblock}
\end{frame}