Tento příspěvek byl poprvé publikován na blogu TK .
Při vývoji frontendu je běžné spotřebovávat data API a používat je k vykreslování uživatelských rozhraní. Někdy však data API nejsou přesně to, s čím chceme pracovat. Daří se nám tedy mapovat data API do stavu aplikace.
Ale nemusí to být složité. Může to být jen jednoduchá funkce mapovače, smlouva o datech API a smlouva o stavu aplikace.
Ukážu příklad v kontextu aplikace Redux a jak můžeme zajistit, aby byl stav konzistentní.
Začínáme počáteční státní smlouvou.
type Person = {
id: number,
name: string,
email: string
};
A použijte typ smlouvy v definici počátečního stavu:
const initialState: Person = {
id: 0,
name: 'TK',
email: '[email protected]'
};
Po definici stavu aplikace můžeme uvažovat o smlouvě API. Můžeme jen implementovat typ PersonAPI
se všemi typy potřebnými pro data.
type PersonAPI = {
id: number,
name: string,
email: string
};
Nyní, když máme naši smlouvu definovanou, můžeme pracovat s mapováním dat. nemusí to být super komplexní třída. Může to být jednoduchá čistá funkce, která přijímá PersonAPI
data a transformovat je do Person
data.
const fromAPI = (person: PersonAPI): Person => ({
id: person.id,
name: person.name,
email: person.email
});
To je docela jednoduché! A jak to používáme?
const payloadAPI = {
id: 1,
name: 'TK',
email: '[email protected]'
};
const person: Person = fromAPI(payloadAPI); // { id: 1, name: 'TK', email: '[email protected]' }
Data přicházejí. Data vycházejí. Všechno čisté.
Zde máme velmi jednoduché mapování, žádná logika. Ale co když data API, která přijímáme, nemají name
, ale firstName
a lastName
? Chceme transformovat firstName
a lastName
do name
atribut v Person
smlouvy.
PersonAPI
typ:
type PersonAPI = {
id: number,
firstName: string,
lastname: string,
email: string
};
Person
typ:
type Person = {
id: number,
name: string,
email: string
};
V našem name
, musíme spojit řetězce. V zásadě se provádí interpolace řetězců:
`${person.firstName} ${person.lastName}`
Naše mapovací funkce by tedy byla něco takového:
const fromAPI = (person: PersonAPI): Person => ({
id: person.id,
name: `${person.firstName} ${person.lastName}`,
email: person.email
});
Skvělý! Transformace dat pro vykreslování uživatelského rozhraní.
Další krok:představte si naše lastName
je volitelný sloupec databáze. Takže koncový bod API to může vrátit... nebo ne!
Můžeme použít Typescript Optional Property
. Říká nám:"Je to volitelná vlastnost, má tento typ, ale data zde mohou být... nebo ne!"
Takže to používáme v naší smlouvě API:
type PersonAPI = {
id: number,
firstName: string,
lastName?: string,
email: string
};
Pěkný! Nyní víme, že pro vytvoření name
musíme udělat nějakou logiku atribut.
- Má
lastName
vlastnost:concatfirstName
alastName
- Nemá
lastName
:stačí vrátitfirstName
hodnotu
const fromAPI = (person: PersonAPI): Person => {
let name: string;
if (person.lastName) {
name = `${person.firstName} ${person.lastName}`
} else {
person.firstName
}
return {
id: person.id,
name,
email: person.email
};
};
Ale můžeme také transformovat tento let
příkaz do const
provedením ternární operace:
const fromAPI = (person: PersonAPI): Person => {
const name: string = person.lastName
? `${person.firstName} ${person.lastName}`
: person.firstName;
return {
id: person.id,
name,
email: person.email
};
};
Nebo lépe:rozdělte jeho odpovědnost do funkce, která buduje jméno!
const buildPersonName = (person: PersonAPI): string =>
person.lastName
? `${person.firstName} ${person.lastName}`
: person.firstName;
const fromAPI = (person: PersonAPI): Person => {
const name: string = buildPersonName(person);
return {
id: person.id,
name,
email: person.email
};
};
Oddělujeme odpovědnost za každou funkci. Skvělý! Nyní je snazší otestovat naše funkce.
Další fáze:použití dat API k vytvoření nového stavu aplikace. Představte si, že chceme vědět, zda je daný člověk aktivní. Obchodní pravidlo zní:stav osoby by měl být active
a poslední návštěva by měla být během tohoto týdne (za posledních 7 dní).
Nejprve naše smlouva o API:
type PersonAPI = {
id: number,
firstName: string,
lastName?: string,
email: string,
status: string,
lastVisit: Date
};
Použijeme tyto vlastnosti:status
a lastVisit
.
Za druhé naše smlouva o stavu aplikace:
type Person = {
id: number,
name: string,
email: string,
active: boolean
};
Obchodní pravidlo nyní:
- Stav osoby by měl být
active
person.status === 'active'
- Poslední návštěva osoby by měla být za posledních 7 dní
person.lastVisit >= new Date(Date.now() - 7 * 24 * 3600 * 1000);
Nyní naše mapovací funkce:
const fromAPI = (person: PersonAPI): Person => {
const name: string = buildPersonName(person);
const active: boolean = person.status === 'active' && person.lastVisit >= new Date(Date.now() - 7 * 24 * 3600 * 1000);
return {
id: person.id,
name,
email: person.email,
active
};
};
Pojďme to zrefaktorovat! Začneme s status
věc. 'active'
je řetězec. K jeho definování v datové struktuře a umožnění opětovné použitelnosti můžeme použít Typescript's Enum.
enum PersonStatus {
Active = 'active',
Inactive = 'inactive'
};
Používáme to takto:
PersonStatus.Active // 'active'
PersonStatus.Inactive // 'inactive'
Logika stavu osoby je s touto funkcí snadná:
person.status === PersonStatus.Active;
Nyní poslední návštěva. Místo náhodných čísel, co to udělat trochu popisnější? Toto je 1 den v milisekundách:
const oneDayInMilliseconds: number = 24 * 3600 * 1000;
Toto je 7 dní v milisekundách:
const sevenDaysInMilliseconds: number = oneDayInMilliseconds * 7;
A to je před týdnem:
const weekAgo: Date = new Date(Date.now() - sevenDaysInMilliseconds);
Nyní je naše logika snadná:
person.lastVisit >= weekAgo;
Nyní můžeme vše spojit do funkce nazvané isActive
to vrací boolean?
const isActive = (person: PersonAPI): boolean => {
const oneDayInMilliseconds: number = 24 * 3600 * 1000;
const sevenDaysInMilliseconds: number = oneDayInMilliseconds * 7;
const weekAgo: Date = new Date(Date.now() - sevenDaysInMilliseconds);
return person.status === PersonStatus.Active &&
person.lastVisit >= weekAgo;
};
Opravdu chci oddělit weekAgo
„logiku“ do nové funkce. A chci také jmenovat výroky.
const getWeekAgo = (): Date => {
const oneDayInMilliseconds: number = 24 * 3600 * 1000;
const sevenDaysInMilliseconds: number = oneDayInMilliseconds * 7;
return new Date(Date.now() - sevenDaysInMilliseconds);
};
const weekAgo: Date = getWeekAgo();
Když pojmenujeme naše prohlášení, vypadá to takto:
const hasActiveStatus: boolean = person.status === PersonStatus.Active;
const lastVisitInSevenDays: boolean = person.lastVisit >= weekAgo;
Takže naše finální isActive
funkce vypadá krásně:
const isActive = (person: PersonAPI): boolean => {
const weekAgo: Date = getWeekAgo();
const hasActiveStatus: boolean = person.status === PersonStatus.Active;
const lastVisitInSevenDays: boolean = person.lastVisit >= weekAgo;
return hasActiveStatus && lastVisitInSevenDays;
};
A naše mapovací funkce je jednoduchá:
const fromAPI = (person: PersonAPI): Person => {
const name: string = buildPersonName(person);
const active: boolean = isActive(person);
return {
id: person.id,
name,
email: person.email,
active
};
};
Několik vylepšení:Zkratka hodnoty vlastnosti objektu pro id
a email
.
const fromAPI = (person: PersonAPI): Person => {
const { id, email }: PersonAPI = person;
const name: string = buildPersonName(person);
const active: boolean = isActive(person);
return {
id,
name,
email,
active
};
};
Učení
Co jsme se tu tedy naučili?
- Datové smlouvy nám pomáhají lépe definovat naše datové struktury, tedy stav, který chceme, aby naše rozhraní správně vykreslovalo uživatelské rozhraní.
- Slouží také jako dobrá dokumentace:lepší porozumění naší odpovědi API a stavu aplikace, se kterým se musíme vypořádat.
- Další skvělou výhodou je, když definujeme datové typy a používáme je v počátečním stavu. Náš systém je skutečně konzistentní, pokud zachováváme státní smlouvu napříč aplikací.
- Nemusí to být složité. Pouze jednoduché a čisté funkce. Oddělte odpovědnost za každou funkci a můžeme jít. Pomáhá nám to i při testování.
Doufám, že jsem mohl ukázat dobrý přehled datových smluv, jednoduchých funkcí a principu jediné odpovědnosti. V softwarovém inženýrství je opravdu snadné udělat vše složité a pokazit to. Ale pokud se pečlivě zamyslíme nad našimi daty, datovými strukturami, které používáme, a tím, jak zvládáme složitost a logiku, myslím, že máme dobrou šanci vybudovat dobrý software.
Zdroje
- Kurz JavaScript pro začátečníky
- Kurz React pro začátečníky
- Pokročilý kurz React
- Kurz ES6
- Kurz JavaScriptu od OneMonth