Rozlišená sjednocení
Na konci minulé lekce jste lektvary od zbraní rozlišovali podle jejich vlastností, pomocí "leceni" in predmet. Funguje to, ale je to křehké: TypeScript si nevšimne překlepu jako "lecen" in predmet, a když jednou vlastnost leceni dostane i jiný druh předmětu, kontrola potichu přestane fungovat.
Obvyklý způsob, jak v TypeScriptu popsat věci několika různých druhů, je dát každému objektu vlastnost, která říká, co je zač. V tomto kurzu se jí bude říkat druh (v anglickém kódu se obvykle jmenuje kind) a v každém typu sjednocení má jiný literálový typ:
type Lektvar = { druh: "lektvar"; nazev: string; leceni: number };
type Zbran = { druh: "zbran"; nazev: string; poskozeni: number };
type Predmet = Lektvar | Zbran;
const inventar: Predmet[] = [
{ druh: "lektvar", nazev: "Léčivý lektvar", leceni: 20 },
{ druh: "zbran", nazev: "Stříbrný meč", poskozeni: 12 },
];Takovému sjednocení se říká rozlišené sjednocení (anglicky discriminated union): všechny jeho typy mají společnou vlastnost druh a podle její hodnoty se od sebe rozliší. Té vlastnosti se také říká značka (anglicky tag). Kontrola značky typ zúží:
function popisPredmetu(predmet: Predmet): string {
if (predmet.druh === "lektvar") {
return `${predmet.nazev} (léčí ${predmet.leceni})`;
} else {
return `${predmet.nazev} (poškození ${predmet.poskozeni})`;
}
}
for (const predmet of inventar) {
console.log(popisPredmetu(predmet));
}Vypíše:
Léčivý lektvar (léčí 20)
Stříbrný meč (poškození 12)Uvnitř if je předmět typu Lektvar, protože druh: "lektvar" mají jen lektvary; ve větvi else je typu Zbran. Vlastnosti, které mají všechny typy ve sjednocení, tady druh a nazev, můžete číst bez kontroly.
Za běhu programu je značka obyčejná vlastnost: console.log(inventar[1]) vypíše { druh: "zbran", nazev: "Stříbrný meč", poskozeni: 12 }. Pro TypeScript je to ale klíč k celému objektu. Ví, které vlastnosti patří ke kterému druhu, a tak kontroluje i vaše data:
type Lektvar = { druh: "lektvar"; nazev: string; leceni: number };
type Zbran = { druh: "zbran"; nazev: string; poskozeni: number };
type Predmet = Lektvar | Zbran;
const korist: Predmet[] = [
{ druh: "lektvar", nazev: "Elixír", poskozeni: 5 },
{ druh: "lektvr", nazev: "Tonikum", leceni: 5 },
];main.ts(6,41): error TS2353: Object literal may only specify known properties, and 'poskozeni' does not exist in type 'Lektvar'.
main.ts(7,7): error TS2820: Type '"lektvr"' is not assignable to type '"lektvar" | "zbran"'. Did you mean '"lektvar"'?Lektvar s poškozením neprojde, protože Lektvar žádné poskozeni nemá. Neprojde ani druh s překlepem a TypeScript k němu dokonce napoví správnou hodnotu. Porovnání s překlepem, třeba predmet.druh === "lektvr", skončí chybou TS2367, kterou znáte: typy se nepřekrývají.
Typické chyby:
- Objekt bez značky.
const tonikum: Predmet = { nazev: "Tonikum", leceni: 5 };skončí chybouerror TS2322: Type '{ nazev: string; leceni: number; }' is not assignable to type 'Predmet'.a druhý řádek hlášky vysvětluje:Property 'druh' is missing in type '{ nazev: string; leceni: number; }' but required in type 'Lektvar'. druh: stringv typech místo literálového typu. Pak může býtdruhjakýkoli text, typy od sebe nerozliší a jeho kontrola nic nezúží: i poif (predmet.druh === "lektvar")je čtenípredmet.lecenichyba.
Jak psát:
Pokyny
Odměna za úkol je buď zlato, nebo předmět. Seznam v editoru už používá oba druhy, jenže TypeScript zatím nezná typ PredmetovaOdmena. Definujte ho pro předmětové odměny ze seznamu: druh: "predmet", nazev (text) a pocet (číslo).
Napište funkci popisOdmeny s parametrem odmena typu Odmena. Pro zlato vrátí třeba 50 zlatých, pro předmět počet a název, třeba 3x Léčivý lektvar. Vypište všechny odměny ze seznamu. Program pak vypíše:
50 zlatých
1x Stříbrný meč
3x Léčivý lektvar
30 zlatýchSečtěte zlato ze všech zlatých odměn a součet vypište za odměnami:
50 zlatých
1x Stříbrný meč
3x Léčivý lektvar
30 zlatých
Zlato celkem: 80Začněte programovat zdarma
1/8