Lekce 3 - Interface (rozhraní) v TypeScriptu

JavaScript TypeScript Interface (rozhraní) v TypeScriptu

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Funkce a třídy v TypeScriptu, jsme se podívali na základy OOP v TypeScriptu. Dnes se podíváme na jednu z nejpraktičtějších, a pokud pracujete s větším množstvím dat, tak i nejproblematič­tějších konstrukcí, které TypeScript do JavaScriptu přináší - interface. Pokud jste nikdy nezavadili o typové programovací jazyky, bude pro vás toto téma ze začátku možná trochu matoucí. Nebojte, slibuji, že s praxí se to zlepší.

Co interface dělá?

Interface neboli rozhraní je jakási smlouva programátora s programem, že proměnná bude obsahovat data nebo metody v požadovaném tvaru. Pokud je váš kód používán ve větším týmu nebo ho používá někdo úplně jiný, pomáhají mu rozhraní porozumět, co vaše metody vyžadují na vstupu. Pokud nedodržíte správný tvar objektu, váš editor, pokud touto funkcí disponuje, vám napoví co je s vaším kódem špatně. Tím se dá docílit bezpečné komunikace mezi několika objekty v programu.

Chápu, že do začátku vám to moc neříká. To je naprosto v pořádku. Pojďme se podívat na příklad.

Založte si soubor karta.ts a do něj napište:

interface Produkt {
        jmeno: string;
        popis: string;
        cena: number;
        skladem: boolean;
}

Toto je interface produktu, který může obsahovat např. nějaký e-shop. Jak je vidět, objekt produkt bude vždy obsahovat vlastnosti jmeno, popis, cena a informaci zda je skladem. Žádnou z těchto vlastností není možné vynechat. Zároveň nám interface stanoví, jaký typ vlastnosti mají. Nyní ho zkusíme použít.

Implementace interface

Chceme-li implementovat interface v nějaké proměnné, provedeme to přidáním dvojtečky za název proměnné, jak je vidět na příkladu:

const plysak: Produkt = {
        jmeno: 'Plyšový medvěd',
        popis: 5,
        cena: 223,
        skladem: true
}

Kód výše vypíše chybu. Níže je ukázka z mého Visual Studia Code:

Chyba při implementaci rozhraní v TypeScriptu ve Visual Studio Code

Jak je vidět, dostal jsem vynadáno, protože vlastnost popis není kompatibilní s typem number. A aby mi to program ještě více usnadnil, tak mi napoví, jak má celý objekt vypadat. Pokud nemáte editor, který by byl schopen hlídat kód za chodu, zkuste aplikaci zkompilovat. Kompilujeme příkazem v příkazové řádce:

tsc karta.ts

Výsledek:

C:\Users\David\Desktop>tsc karta.ts
karta.ts:8:7 - error TS2322: Type '{ jmeno: string; popis: number; cena: number; sk
ladem: true; }' is not assignable to type 'Produkt'.
  Types of property 'popis' are incompatible.
    Type 'number' is not assignable to type 'string'.

8 const plysak: Produkt = {

Dostaneme stejnou chybu, jakou by nám nahlásil editor. Vše je tedy v pořádku, i bez chytrého editoru jsme chybu odhalili a můžeme si jí opravit:

const plysak: Produkt = {
        jmeno: 'Plyšový medvěd',
        popis: 'Plyšová hračka zobrazující medvěda v sedu.',
        cena: 223,
        skladem: true
}

Interface jako typ parametru funkce

Nyní si ukážeme hlavní důvod proč interface používat. Vytvoříme si funkci a využijeme interface, aby nám pohlídal, co jí předáváme:

function zobrazKartu(produkt: Produkt) {

        const element = document.getElementById('karta');
        const karta = `
                <h3 class="header">${produkt.jmeno}</h3>
                <p class="popis">${produkt.popis}</p>
                <p class="sklad">${produkt.skladem ? 'skladem' : 'vyprodáno'}</p>
                <p class="cena">cena: ${produkt.cena}</p>
        `;

        element.innerHTML = karta;
}

Funkce vytváří kartu pro zobrazení našeho medvěda. V rychlosti si ji popíšeme. Naše funkce si najde element s id="karta" a vytvoří kartu z našich dat. Všimněte si, že pro vygenerování HTML kódu používáme víceřádkový řetězec, deklarovaný pomocí zpětných uvozovek. Proměnné nebo výrazy do řetězce vkládáme pomocí interpolace, tedy sekvencí ${promenna nebo vyraz}. S pomocí ternárního operátoru se rozhoduje, zda se v položce "skladem" zobrazí "skladem" nebo "vyprodáno". Nakonec nám to funkce vše vypíše do <div>u s id="karta".

Jak je vidět, v parametrech funkce je u parametru produkt definováno, že jde o typ Produkt. Díky tomu není možné do funkce vložit jiné objekty než ty obsahující vlastnosti a metody, které určuje dané rozhraní. Když budou obsahovat něco navíc, nebude to vadit.

Možná si myslíte, že není třeba, aby za vás strukturu objektu TypeScript hlídal a že jde jen o zbytečný kód navíc. Věřte mi, že jakmile váš projekt dosáhne určité velikosti, budete rádi, že jste si interface napsali. Jinak se nic nemění, vše ostatní je starý známý JavaScript.

Nepovinná vlastnost

V praxi se nám může stát, že budeme potřebovat definovat nepovinnou vlastnost. K tomuto účelu použijeme otazník. Rozšíříme náš produkt o vlastnost barva?. Ne každý produkt musí mít tuto vlastnost, takže ji zadáme jako nepovinnou:

interface Produkt {
        jmeno: string;
        popis: string;
        cena: number;
        skladem: boolean;
        barva?: string;
}

Nyní pokud u našeho medvídka specifikujeme, nebo úplně vynecháme vlastnost barva?, kompiler to stále vyhodnotí jako validní kód. Nesmíme zapomenout, že TypeScript za nás sice hlídá náš kód, ale pokud vlastnosti barva? nenastavíme hodnotu, bude undefined. Při generování naší karty je tedy třeba tento případ ošetřit:

function zobrazKartu(produkt: Produkt): void {

        const element = document.getElementById('karta');
        const karta = `
                <h3 class="header">${produkt.jmeno}</h3>
                <p class="popis">${produkt.popis}</p>
                ${produkt.barva ? `<p class="barva">barva: ${produkt.barva}</p>` : ''}
                <p class="sklad">${produkt.skladem ? 'skladem' : 'vyprodáno'}</p>
                <p class="cena">cena: ${produkt.cena}</p>
        `;

        element.innerHTML = karta;
}

Ošetření zadání barvy jsme provedli přes další ternární výraz. Na kódu si můžete všimnout ještě jedné zajímavé věci. Za definicí parametrů funkce přibylo : void. TypeScript nám totiž neumožňuje pouze hlídat vstupní atributy, ale i to, co naše funkce vrací. V tomto případě nevrací nic, zadáme tedy void. Možná vás zajímá, jak vypadá JavaScript kód po kompilaci. Možná vás to trochu překvapí.

function zobrazKartu(produkt) {
  var element = document.getElementById("karta");
  var karta =
    '\n        <h3 class="header">' +
    produkt.jmeno +
    '</h3>\n        <p class="popis">' +
    produkt.popis +
    "</p>\n        " +
    (produkt.barva ? '<p class="barva">barva: ' + produkt.barva + "</p>" : "") +
    '\n        <p class="sklad">' +
    (produkt.skladem ? "skladem" : "vyprodáno") +
    '</p>\n        <p class="cena">cena: ' +
    produkt.cena +
    "</p>\n    ";
  element.innerHTML = karta;
}

var plysak = {
  jmeno: "Plyšový medvěd",
  popis: "Plyšová hračka zobrazující medvěda v sedu.",
  cena: 223,
  barva: "modra",
  skladem: true
};

zobrazKartu(plysak);

Toto je celý náš kód po kompilaci. Jak vidíme, všechny interface a označení typů jsou pryč. JavaScript jako takový nemá žádný ekvivalent pro tuto funkcionalitu a je tedy kompilátorem vynechána. Interface slouží pro vaše pohodlí a kontrolu před a během kompilace.

Implementace rozhraní ve třídách

Možná vás napadlo, k čemu vlastně rozhraní je, když jsme stejně tak dobře mohli definovat třídu Produkt a předat funkci zobrazKartu() její instanci. Také by přeci bylo zkontrolováno, zda je parametr opravdu instance třídy Produkt a zda má všechny vlastnosti a metody, které třída deklaruje. My ovšem nechceme omezit parametr na jednu třídu, pouze chceme zkontrolovat, zda parametr něco obsahuje, ale může být jakéhokoli typu. Rozhraní nás na konkrétní typ neomezují.

Udělejme si další příklad a implementujme si rozhraní tentokrát ve třídě, místo v anonymním objektu.

Každá třída může implementovat libovolný počet rozhraní, což by se nemělo plést s dědičností, při které můžeme dědit vždy jen z jedné třídy.

Abychom si rozhraní pořádně vyzkoušeli, vytvoříme si ještě druhé rozhraní a rovnou dvě třídy, reprezentující produkt. Následně si vyzkoušíme, že instance obou tříd můžeme funkci zobrazKartu() předat a to i když to jsou jiné datové typy, ovšem implementující stejné rozhraní.

interface Produkt {
        jmeno: string;
        popis: string;
        cena: number;
        skladem: boolean;
        barva?: string;
}

interface Hodnotitelny {
        celkoveHodnoceni: number;
        pocetHodnoticich: number;
}

class ProduktEshopuA implements Produkt, Hodnotitelny {
        jmeno: string;
        popis: string;
        cena: number;
        skladem: boolean;
        celkoveHodnoceni: number;
        pocetHodnoticich: number;

}

class ProduktEshopuB implements Produkt {
        jmeno: string;
        popis: string;
        cena: number;
        skladem: boolean;
        barva: string;
        pocetKomentaru: number;
}

class Clanek implements Hodnotitelny {
        titulek: string;
        text: string;
        celkoveHodnoceni: number;
        pocetHodnoticich: number;
}

function zobrazKartu(produkt: Produkt): void {

        const element = document.getElementById('karta');
        const karta = `
                <h3 class="header">${produkt.jmeno}</h3>
                <p class="popis">${produkt.popis}</p>
                ${produkt.barva ? `<p class="barva">barva: ${produkt.barva}</p>` : ''}
                <p class="sklad">${produkt.skladem ? 'skladem' : 'vyprodáno'}</p>
                <p class="cena">cena: ${produkt.cena}</p>
        `;

        element.innerHTML = karta;
}

function zobrazHodnoceni(obj: Hodnotitelny): void {
        const element = document.getElementById('hodnoceni');
        const hodnoceni = `
                <p class="hodnoceni">${obj.celkoveHodnoceni}</p>
                <p class="hodnotilo">hidnotilo: ${obj.pocetHodnoticich}</p>
        `;

        element.innerHTML = hodnoceni;
}

zobrazKartu(new ProduktEshopuA())
zobrazKartu(new ProduktEshopuB())

Produkty by bylo samozřejmě ideální nejprve naplnit daty, ale vidíme, že kód se přeložil.

Kromě rozhraní Produkt jsme ještě přidali druhé rozhraní, Hodnotitelny. To můžeme implementovat ve třídách, které lze hodnotit a pak těmto objektům hodnocení jednoduše vypsat jednou univerzální funkcí (ať je to již produkt nebo třeba článek). Díky rozhraní to bude hračka.

Co se týká produktů, můžete si představit, že jedna třída je produkt z eshopu A a druhá je produkt z e-shopu B. Oba tyto obchody mají trochu jiné produkty, ale díky dodržení jednotného rozhraní si mohou mezi sebou tyto obchody vyměňovat javascriptové knihovny, které s jejich produkty vždy korektně fungují.

Rozhraní ve třídě implementujeme pomocí klíčového slova implements. Pokud třída ještě z nějaké jiné třídy dědí, obvykle to zapíšeme jako class A extends B, implements C, D.

A co pole?

Na závěr si ukažme jak využít rozhraní pro práci s polem. Podobně jako definujeme interface objektů, můžeme definovat interface pro pole a to jak pro klíč, tak pro hodnotu.

interface seznam {
        [index: number]: string;
}

// chyba: Type 'number' is not assignable to type 'string'.
let list2: seznam = ["Honza", 2, "Petr"];

Výše jsme si definovali rozhraní seznam pro pole o číselných klíčích a textových položkách. Následné vytvoření takového seznamu, avšak s jednou číselnou položkou, vyvolá při překladu do TypeScriptu chybu.

Zkusme si ještě poslední příklad:

interface narozeniny {
        [index: string]: number;
}

let seznamOslavencu: narozeniny;
// v pořádku
seznamOslavencu["Honza"] = 1505;
// chyba: Type '"patnáctéhokvětna"' is not assignable to type 'number'.
seznamOslavencu[2] = 'patnáctéhokvětna';

Zde je to opačně, klíče jsou textové a hodnoty číselné. Poslední řádek tedy vyvolá chybu zatímco ten předchozí se provede.

Snad vám použití interface již nyní dává smysl. Jedná se opravdu o kontrolu zda daný typ něco splňuje, ale ne, zda je to nějaký konkrétní typ. Určitě se s nimi budeme setkávat ještě často. V příloze najdete plně funkční kartu našeho medvídka i se styly. Jen jediné co nechám na vás, je kompilace z TypeScriptu :-)

V příští lekci, , se podíváme na generika.


 

Stáhnout

Staženo 6x (1.27 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

 

Článek pro vás napsal Jiří Kvapil
Avatar
Jak se ti líbí článek?
2 hlasů
Autor se věnuje profesionálně front-endu a jezdí na všem co má kola.
Miniatura
Předchozí článek
Funkce a třídy v TypeScriptu
Miniatura
Všechny články v sekci
TypeScript
Aktivity (2)

 

 

Komentáře

Avatar
Petr Novák
Člen
Avatar
Petr Novák:20. července 15:51

Zdravím autora.
Další zajímavý seriál na ITN. Mohu vědět,kdy bude pokračování a jak cca často?
Díky za odpověď i za seriál.

 
Odpovědět 20. července 15:51
Avatar
Petr Novák
Člen
Avatar
Petr Novák:31. srpna 7:25

No je škoda,že autor seriálu ani nereaguje,jestli přibudou nové díly či ne.Škoda.

 
Odpovědět 31. srpna 7:25
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Petr Novák
David Čápka:31. srpna 11:22

Autoři mají zaměstnání a rodiny a když přestanou zvládat ještě k tomu psaní článků, musí někdo najít nového autora, domluvit to s ním a pak mu za práci zaplatit. Toto léto jsme přidali možná vůbec nejvíce nových článků v historii sítě a další teď není z čeho platit. Jestli chceš urychlit vývoj sítě, můžeš jí finančně podpořit dobitím bodů. Taková akce by měla určitě reálný efekt. Psaním takovýchto příspěvků nedocílíš naopak vůbec ničeho.

Odpovědět 31. srpna 11:22
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Petr Novák
Člen
Avatar
Odpovídá na David Čápka
Petr Novák:31. srpna 11:36

No cílem bylo dostat info. Třeba ve smyslu "nestíhám" nebo cokoliv.Takže to má brát tak,že původní autor tento seriál nebude dále dělat a hledá se nový,ale toho je třeba finančně podpořit?

 
Odpovědět 31. srpna 11:36
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Petr Novák
David Čápka:31. srpna 11:50

Kdyby stíhal, tak to tu pokračuje. Nehledá se nový, i jen to hledání stojí čas a peníze několika lidí. Teď se neděje nic. Až bude dostatek peněz z prodeje prémiového obsahu, tak se to bude řešit. Záleží jen na vás, kdy to bude. Finančně podporujeme naprostou většinu autorů, i současného autora TypeScript kurzu, který si finanční odměnu zatím ani nevyzvedl, což nasvědčuje tomu, že asi opravdu nemá čas :)

Odpovědět 31. srpna 11:50
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 5 zpráv z 5.