Typescript
V této části se podíváme na Typescript.
Typescript je silně typovaný open-source jazyk od Microsoftu, který je nádstavbou nad Javascriptem a má za úkol řešit problém chyb odhalitelných až za běhu - např. změnou typu vstupní proměnné.
Nové projekty doporučuji vždy vytvářet v Typescriptu s co nejpřísnějším nastavením na všechno. Ušetříte si tím problémy.
🎯 Co je Typescript?
Typescript je nadstavba JavaScriptu, která umožňuje psát kód s tzv. “statickým typováním”.
To znamená, že transpilátor (tj. nástroj, který převádí Typescript kód na JavaScript) zkontroluje, že všechny proměnné, funkce a třídy mají správné typy a že jsou použity správně.
Tímto způsobem můžeme snadno eliminovat chyby v kódu, které by se při běhu aplikace hledaly složitěji, např. při zapomenutí na zpracování uživatelského vstupu, které zhodí celou aplikaci.
- Pravděpodobně už jsi někde viděl soubory v Typescriptu s příponou
.tsnebo.tsx. Ty se ve výsledném kódu překládají (transpilují) zpět do Javascriptu.
🛠️ Instalace
Díky použití Vite nemusíme instalaci vůbec řešit, protože Vite nám umí Typescript projekt jednoduše připravit;
# Na vyzvání zvolíme React + Typescript
npx create vite ./L4-projekt-zamestnanci
Pokud chceme přidat Typescript do existujícího projektu (see ):
# Nainstalujeme typescript a definice typů pro React
npm install —save-dev @types/react @types/react-dom @types/node @types/jest
Potom klasicky
- nakonfigurujeme
tsconfig.json - přidáme plugin do Vite
a můžeme pracovat.
1️⃣ Základy Typescriptu v Reactu
Pro úplný začátek s Typestriptem doporučuji projít oficiální dokumentaci nebo se podívat na W3Schools Typescript tutorial.
Teď se podívejme na použití Typescriptu pro základní komponenty a hooky, které už známe:
Komponenty
U komponent typujeme zejména
propsa případněchildren.
type ButtonProps = {
label: string;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
};
export function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>;
}
Pokud chceme komponentu s children:
type CardProps = {
title?: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<section>
{title && <h3>{title}</h3>}
{children}
</section>
);
}
Nebo můžeme použít PropsWithChildren utilitu:
type CardProps = {
title?: string;
};
export function Card({ title, children }: PropsWithChildren<CardProps>) {
return (
<section>
{title && <h3>{title}</h3>}
{children}
</section>
);
}
useState hook
useStatemůžeme typovat tzv. generikem (generic), tj. umožní nám zadat parametr, který ovlivní používané typy uvnitř funkce.
// říkáme, že count bude vždy číslo
const [count, setCount] = useState<number>(0);
type User = {
id: string;
name: string
};
// Uživatel bude daná struktura nebo null
const [user, setUser] = useState<User | null>(null);
setCount((prev) => prev + 1);
setUser({ id: "1", name: "Alice" });
useEffect
useEfecttypicky nepotřebuje zvláštní anotace; typy se odvodí. Důležité je správně typovat hodnoty, se kterými pracujeme (např.setInterval).
const [tick, setTick] = useState<number>(0);
useEffect(() => {
// Jednorázově (koukněte na prázdné pole závislostí) nastaví pravidelnou akci, která zvýší počet sekund každou (1000 ms)
const id = window.setInterval(() => setTick((t) => t + 1), 1000);
// Po unmountu se interval zruší (uvolní výkon, paměť)
return () => window.clearInterval(id);
}, []);
U asynchronní logiky v efektu nepouštíme async přímo na callback, ale uvnitř něj:
type Todo = { id: number; title: string };
const [todos, setTodos] = useState<Todo[]>([]);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
let cancelled = false;
const run = async () => {
setLoading(true);
try {
const res = await fetch("/api/todos");
const data: Todo[] = await res.json();
if (!cancelled) setTodos(data);
} finally {
if (!cancelled) setLoading(false);
}
};
run();
return () => {
cancelled = true;
};
}, []);
createContext a useContext
U kontextu je dobré typovat hodnotu a dovolit undefined mimo provider. Pak si vytvoříme bezpečný hook.
type User = {
id: string;
name: string
};
type AuthContextValue = {
user: User | null;
login: (u: User) => void;
logout: () => void;
};
// defiinujeme strukturu hodnot, které v kontextu budou
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const value = useMemo<AuthContextValue>(
() => ({
user,
login: (u) => setUser(u),
logout: () => setUser(null),
}),
[user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
/**
* Vlastní hook pro načtení uživatelských dat
*/
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) {
throw new Error("useAuth must be used within <AuthProvider>");
}
return ctx;
}
// Použití v komponentě
function Profile() {
const { user, logout } = useAuth();
if (!user) return <button onClick={logout}>Odhlásit</button>;
return (
<div>
<p>Ahoj {user.name}!</p>
<button onClick={logout}>Odhlásit</button>
</div>
);
}