Avage TypeScripti kaardistatud tüüpide jõud dünaamiliste objektide teisenduste ja paindlike omaduste muudatuste jaoks, suurendades koodi taaskasutatavust ja tüübikindlust globaalsete arendajate jaoks.
TypeScripti kaardistatud tüübid: objektide teisendamise ja omaduste muutmise valdamine
Tarkvaraarenduse pidevalt arenevas maastikus on tugevad tüübisüsteemid ülimalt olulised hooldatavate, skaleeritavate ja usaldusväärsete rakenduste loomiseks. TypeScript on oma võimsa tüübi järeldamise ja täiustatud funktsioonidega muutunud asendamatuks tööriistaks arendajatele kogu maailmas. Üks selle kõige võimsamaid võimalusi on Kaardistatud tüübid, keerukas mehhanism, mis võimaldab meil teisendada olemasolevaid objektitüüpe uuteks. See blogipostitus sukeldub sügavale TypeScripti kaardistatud tüüpide maailma, uurides nende põhimõisteid, praktilisi rakendusi ja seda, kuidas need võimaldavad arendajatel elegantselt käsitleda objektide teisendusi ja omaduste muudatusi.
Kaardistatud tüüpide põhimõtte mõistmine
Põhimõtteliselt on kaardistatud tüüp viis luua uusi tüüpe, itereerides olemasoleva tüübi omaduste kaudu. Mõelge sellele kui tüüpide silmusele. Iga algse tüübi omaduse jaoks saate rakendada teisenduse selle võtmele, väärtusele või mõlemale. See avab tohutult palju võimalusi uute tüübimääratluste genereerimiseks olemasolevate põhjal, ilma käsitsi kordamiseta.
Kaardistatud tüübi põhiline süntaks hõlmab struktuuri { [P in K]: T }, kus:
P: tähistab itereeritava omaduse nime.in K: see on oluline osa, mis näitab, etPvõtab iga võtme tüübistK(mis on tavaliselt stringiliteraalide liit või keyof tüüp).T: määratleb omadusePväärtuse tüübi uues tüübis.
Alustame lihtsa illustratsiooniga. Kujutage ette, et teil on kasutajaandmeid esindav objekt ja soovite luua uue tüübi, kus kõik omadused on valikulised. See on tavaline stsenaarium, näiteks konfiguratsiooniobjektide loomisel või osaliste värskenduste rakendamisel.
Näide 1: Kõigi omaduste valikuliseks muutmine
Kaaluge seda baastüüpi:
type User = {
id: number;
name: string;
email: string;
isActive: boolean;
};
Saame luua uue tüübi OptionalUser, kus kõik need omadused on valikulised, kasutades kaardistatud tüüpi:
type OptionalUser = {
[P in keyof User]?: User[P];
};
Jaotame selle osadeks:
keyof User: see genereerib tüübiUservõtmete liidu (nt'id' | 'name' | 'email' | 'isActive').P in keyof User: see itereerib iga võtme üle liidus.?: see on modifikaator, mis muudab omaduse valikuliseks.User[P]: see on otsingutüüp. Iga võtmePjaoks hangib see vastava väärtuse tüübi algsest tüübistUser.
Tulemuseks olev tüüp OptionalUser näeks välja selline:
{
id?: number;
name?: string;
email?: string;
isActive?: boolean;
}
See on uskumatult võimas. Selle asemel, et iga omadust käsitsi ?-ga uuesti määratleda, oleme tüübi dünaamiliselt genereerinud. Seda põhimõtet saab laiendada, et luua palju muid utiliidi tüüpe.
Kaardistatud tüüpide levinud omaduste modifikaatorid
Kaardistatud tüübid ei tähenda ainult omaduste valikuliseks muutmist. Need võimaldavad teil rakendada erinevaid modifikaatoreid tulemuseks oleva tüübi omadustele. Kõige levinumad on:
- Valikulisus: modifikaatori
?lisamine või eemaldamine. - Ainult lugemiseks: modifikaatori
readonlylisamine või eemaldamine. - Nullitavus/Mittenullitavus:
| nullvõi| undefinedlisamine või eemaldamine.
Näide 2: Tüübi ainult lugemiseks versiooni loomine
Sarnaselt omaduste valikuliseks muutmisega saame luua tüübi ReadonlyUser:
type ReadonlyUser = {
readonly [P in keyof User]: User[P];
};
See genereerib:
{
readonly id: number;
readonly name: string;
readonly email: string;
readonly isActive: boolean;
}
See on äärmiselt kasulik tagamaks, et teatud andmestruktuure ei saa pärast loomist muuta, mis on oluline põhimõte tugevate ja prognoositavate süsteemide loomiseks, eriti samaaegsetes keskkondades või kui tegemist on muutumatute andmemustritega, mis on populaarsed funktsionaalse programmeerimise paradigmades, mida on vastu võtnud paljud rahvusvahelised arendusmeeskonnad.
Näide 3: Valikulisuse ja ainult lugemise kombineerimine
Saame kombineerida modifikaatoreid. Näiteks tüüp, kus omadused on nii valikulised kui ka ainult lugemiseks:
type OptionalReadonlyUser = {
readonly [P in keyof User]?: User[P];
};
Selle tulemuseks on:
{
readonly id?: number;
readonly name?: string;
readonly email?: string;
readonly isActive?: boolean;
}
Modifikaatorite eemaldamine kaardistatud tüüpidega
Mis siis, kui soovite modifikaatori eemaldada? TypeScript võimaldab seda kasutada süntaksit -? ja -readonly kaardistatud tüüpides. See on eriti võimas, kui tegemist on olemasolevate utiliidi tüüpide või keeruliste tüüpide kompositsioonidega.
Oletame, et teil on tüüp Partial<T> (mis on sisseehitatud ja muudab kõik omadused valikuliseks) ja soovite luua tüübi, mis on sama, mis Partial<T>, kuid millel on kõik omadused jälle kohustuslikuks muudetud.
type Mandatory<T> = {
-?: T extends object ? T[keyof T] : never;
};
type FullyPopulatedUser = Mandatory<Partial<User>>;
See tundub vastupidine. Analüüsime seda:
Partial<User> on samaväärne meie OptionalUser-iga. Nüüd soovime muuta selle omadused kohustuslikuks. Süntaks -? eemaldab valikulise modifikaatori.
Otsesem viis selle saavutamiseks, ilma et peaksite esmalt Partial-it kasutama, on lihtsalt võtta algne tüüp ja muuta see kohustuslikuks, kui see oli valikuline:
type MakeMandatory<T> = {
-?: T;
};
type MandatoryUser = MakeMandatory<OptionalUser>;
See taastab korrektselt OptionalUser algse User tüübi struktuuri (kõik omadused on olemas ja nõutavad).
Sarnaselt, et eemaldada modifikaator readonly:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type MutableUser = Mutable<ReadonlyUser>;
MutableUser on samaväärne algse tüübiga User, kuid selle omadused ei ole ainult lugemiseks.
Nullitavus ja määratlematus
Saate kontrollida ka nullitavust. Näiteks veendumaks, et kõik omadused on kindlasti mittenullitavad:
type NonNullableValues<T> = {
[P in keyof T]-?: NonNullable<T[P]>;
};
interface MaybeNull {
name: string | null;
age: number | undefined;
}
type DefiniteValues = NonNullableValues<MaybeNull>;
// type DefiniteValues = {
// name: string;
// age: number;
// }
Siin tagab -?, et omadused ei ole valikulised, ja NonNullable<T[P]> eemaldab väärtuse tüübist null ja undefined.
Omaduste võtmete teisendamine
Kaardistatud tüübid on uskumatult mitmekülgsed ja need ei piirdu ainult väärtuste või modifikaatorite muutmisega. Saate teisendada ka objektitüübi võtmeid. Siin paistavad kaardistatud tüübid keerukates stsenaariumides tõeliselt silma.
Näide 4: Omaduste võtmete prefikseerimine
Oletame, et soovite luua uue tüübi, kus kõigil olemasoleva tüübi omadustel on kindel eesliide. See võib olla kasulik nimeruumide jaoks või andmestruktuuride variatsioonide genereerimiseks.
type Prefixed<T, Prefix extends string> = {
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};
type OriginalConfig = {
timeout: number;
retries: number;
};
type PrefixedConfig = Prefixed<OriginalConfig, 'app'>;
// type PrefixedConfig = {
// appTimeout: number;
// appRetries: number;
// }
Lahkame võtmeteisendust:
P in keyof T: itereerib endiselt üle algsete võtmete.as `${Prefix}${Capitalize<string & P>}`: see on võtme ümberkaardistamise klausel.`${Prefix}${...}`: see kasutab malliliteratsiooni tüüpe uue võtmenime konstrueerimiseks, ühendades pakutavaPrefixteisendatud omaduse nimega.Capitalize<string & P>: see on tavaline muster tagamaks, et omaduse nimePkäsitletakse stringina ja seejärel suurtähtedega. Kasutamestring & P, et lõigataPstringiga, tagades, et TypeScript käsitleb seda stringitüübina, mis on vajalikCapitalizejaoks.
See näide näitab, kuidas saate omadusi dünaamiliselt ümber nimetada olemasolevate põhjal, mis on võimas tehnika järjepidevuse säilitamiseks rakenduse erinevates kihtides või integreerumisel väliste süsteemidega, millel on konkreetsed nimetamiskonventsioonid.
Näide 5: Omaduste filtreerimine
Mis siis, kui soovite kaasata ainult omadusi, mis vastavad teatud tingimusele? Selle saab saavutada, kombineerides kaardistatud tüübid Tingimuslike tüüpidega ja klausliga as võtmete ümberkaardistamiseks, sageli omaduste väljafiltreerimiseks.
type OnlyStrings<T> = {
[P in keyof T as T[P] extends string ? P : never]: T[P];
};
interface MixedData {
name: string;
age: number;
city: string;
isActive: boolean;
}
type StringOnlyData = OnlyStrings<MixedData>;
// type StringOnlyData = {
// name: string;
// city: string;
// }
Sel juhul:
T[P] extends string ? P : never: iga omadusePjaoks kontrollime, kas selle väärtuse tüüp (T[P]) on määratavstring-ile.- Kui see on string, siis võti
Psäilitatakse. - Kui see ei ole string, siis kaardistatakse see
never-ile. Kui võti kaardistataksenever-ile, siis see eemaldatakse tegelikult tulemuseks olevast objektitüübist.
See tehnika on hindamatu konkreetsemate tüüpide loomiseks laiematest tüüpidest, näiteks ainult teatud tüüpi konfiguratsiooniseadete väljavõtmiseks või andmeväljade eraldamiseks nende olemuse järgi.
Näide 6: Võtmete teisendamine erinevaks kujuks
Saate teisendada ka võtmeid täiesti erinevat tüüpi võtmeteks, näiteks muuta stringvõtmed numbriteks või vastupidi, kuigi seda kasutatakse vähem otseseks objektide manipuleerimiseks ja rohkem täiustatud tüübitasandi programmeerimiseks.
Kaaluge stringvõtmete muutmist stringiliteraalide liiduks ja seejärel selle kasutamist uue tüübi alusena. Kuigi see ei teisenda objekti võtmeid *kaardistatud tüübi sees* sel viisil otse, näitab see, kuidas võtmeid saab manipuleerida.
Otsesem võtmeteisenduse näide võiks olla võtmete kaardistamine nende suurtähtedega versioonidele:
type UppercaseKeys<T> = {
[P in keyof T as Uppercase<string & P>]: T[P];
};
type LowercaseData = {
firstName: string;
lastName: string;
};
type UppercaseData = UppercaseKeys<LowercaseData>;
// type UppercaseData = {
// FIRSTNAME: string;
// LASTNAME: string;
// }
See kasutab klauslit as iga võtme P teisendamiseks selle suurtähtedega ekvivalendiks.
Praktilised rakendused ja reaalsed stsenaariumid
Kaardistatud tüübid ei ole ainult teoreetilised konstruktsioonid; neil on olulised praktilised tagajärjed erinevates arendusvaldkondades. Siin on mõned levinud stsenaariumid, kus need on hindamatud:
1. Korduvkasutatavate utiliidi tüüpide loomine
Paljusid levinud tüübiteisendusi saab kapseldada korduvkasutatavatesse utiliidi tüüpidesse. TypeScripti standardteek pakub juba suurepäraseid näiteid, nagu Partial<T>, Readonly<T>, Record<K, T> ja Pick<T, K>. Saate määratleda oma kohandatud utiliidi tüübid, kasutades kaardistatud tüüpe, et oma arendustöövoogu sujuvamaks muuta.
Näiteks tüüp, mis kaardistab kõik omadused funktsioonidele, mis aktsepteerivad algset väärtust ja tagastavad uue väärtuse:
type Mappers<T> = {
[P in keyof T]: (value: T[P]) => T[P];
};
interface ProductInfo {
name: string;
price: number;
}
type ProductMappers = Mappers<ProductInfo>;
// type ProductMappers = {
// name: (value: string) => string;
// price: (value: number) => number;
// }
2. Dünaamiline vormide käsitlemine ja valideerimine
Veebiarenduses, eriti raamistikega nagu React või Angular (kuigi siinsed näited on puhas TypeScript), on vormide ja nende valideerimisolekute käsitlemine tavaline ülesanne. Kaardistatud tüübid aitavad hallata iga vormivälja valideerimise olekut.
Kaaluge vormi, mille väljad võivad olla 'puhtad', 'puudutatud', 'kehtivad' või 'kehtetud'.
type FormFieldState = 'pristine' | 'touched' | 'dirty' | 'valid' | 'invalid';
type FormState<T> = {
[P in keyof T]: FormFieldState;
};
interface UserForm {
username: string;
email: string;
password: string;
}
type UserFormState = FormState<UserForm>;
// type UserFormState = {
// username: FormFieldState;
// email: FormFieldState;
// password: FormFieldState;
// }
See võimaldab teil luua tüübi, mis peegeldab teie vormi andmestruktuuri, kuid selle asemel jälgib iga välja olekut, tagades teie vormi haldusloogika järjepidevuse ja tüübikindluse. See on eriti kasulik rahvusvahelistes projektides, kus mitmekesised UI/UX nõuded võivad viia keerukate vormiolekute juurde.
3. API vastuse teisendamine
API-dega tegelemisel ei pruugi vastuse andmed alati teie sisemiste domeenimudelitega ideaalselt ühtida. Kaardistatud tüübid aitavad API vastuseid soovitud kuju teisendada.
Kujutage ette API vastust, mis kasutab võtmete jaoks snake_case-i, kuid teie rakendus eelistab camelCase-i:
// Oletame, et see on sissetuleva API vastuse tüüp
type ApiUserData = {
user_id: number;
first_name: string;
last_name: string;
};
// Abiline snake_case-i camelCase-iks teisendamiseks võtmete jaoks
type ToCamelCase<S extends string>: string = S extends `${infer T}_${infer U}`
? `${T}${Capitalize<U>}`
: S;
type CamelCasedKeys<T> = {
[P in keyof T as ToCamelCase<string & P>]: T[P];
};
type AppUserData = CamelCasedKeys<ApiUserData>;
// type AppUserData = {
// userId: number;
// firstName: string;
// lastName: string;
// }
See on keerukam näide, mis kasutab rekursiivset tingimuslikku tüüpi stringide manipuleerimiseks. Peamine järeldus on see, et kaardistatud tüübid, kui neid kombineerida muude täiustatud TypeScripti funktsioonidega, saavad automatiseerida keerukaid andmeteisendusi, säästes arendusaega ja vähendades käitusajal esinevate vigade riski. See on ülioluline globaalsetele meeskondadele, kes töötavad erinevate serveripoolsete teenustega.
4. Enum-like struktuuride täiustamine
Kuigi TypeScriptil on `enum`s, võite mõnikord soovida rohkem paindlikkust või tuletada tüüpe objektiliteraalidest, mis käituvad nagu enumid.
const AppPermissions = {
READ: 'read',
WRITE: 'write',
DELETE: 'delete',
ADMIN: 'admin',
} as const;
type Permission = typeof AppPermissions[keyof typeof AppPermissions];
// type Permission = 'read' | 'write' | 'delete' | 'admin'
type UserPermissions = {
[P in Permission]?: boolean;
};
type RolePermissions = {
[P in Permission]: boolean;
};
const userPerms: UserPermissions = {
read: true,
};
const adminRole: RolePermissions = {
read: true,
write: true,
delete: true,
admin: true,
};
Siin tuletame esmalt kõigi võimalike lubade stringide liidu tüübi. Seejärel kasutame kaardistatud tüüpe, et luua tüüpe, kus iga luba on võti, mis võimaldab meil määrata, kas kasutajal on see luba (valikuline) või kas roll seda nõuab (nõutav). See muster on tavaline autoriseerimissüsteemides kogu maailmas.
Väljakutsed ja kaalutlused
Kuigi kaardistatud tüübid on uskumatult võimsad, on oluline olla teadlik võimalikest keerukustest:
- Loetavus ja keerukus: liiga keerulised kaardistatud tüübid võivad muutuda raskesti loetavaks ja mõistetavaks, eriti arendajatele, kes on nende täiustatud funktsioonidega uued. Püüdke alati selguse poole ja kaaluge kommentaaride lisamist või keerukate teisenduste jaotamist.
- Jõudluse mõju: Kuigi TypeScripti tüübi kontrollimine toimub kompileerimise ajal, võivad äärmiselt keerulised tüüpide manipuleerimised teoreetiliselt veidi suurendada kompileerimisaega. Enamiku rakenduste puhul on see tühine, kuid seda tuleks meeles pidada väga suurte koodibaaside või väga jõudluskriitiliste ehitusprotsesside puhul.
- Silumine: Kui kaardistatud tüüp genereerib ootamatu tulemuse, võib silumine mõnikord olla keeruline. TypeScript Playgroundi või IDE tüübi kontrollimise funktsioonide kasutamine on ülioluline mõistmaks, kuidas tüüpe lahendatakse.
- `keyof` ja otsingutüüpide mõistmine: Kaardistatud tüüpide tõhus kasutamine sõltub kindlast arusaamast `keyof` ja otsingutüüpidest (`T[P]`). Veenduge, et teie meeskonnal oleks hea arusaam neist põhimõistetest.
Parimad tavad kaardistatud tüüpide kasutamiseks
Kaardistatud tüüpide täieliku potentsiaali kasutamiseks, leevendades samal ajal nende väljakutseid, kaaluge neid parimaid tavasid:
- Alustage lihtsast: alustage põhilise valikulisuse ja ainult lugemiseks teisendustega, enne kui sukeldute keerukasse võtmete ümberkaardistamisse või tingimuslikku loogikasse.
- Kasutage sisseehitatud utiliidi tüüpe: tutvuge TypeScripti sisseehitatud utiliidi tüüpidega, nagu
Partial,Readonly,Record,Pick,OmitjaExclude. Need on sageli piisavad tavaliste ülesannete jaoks ning on hästi testitud ja mõistetud. - Looge korduvkasutatavaid üldtüüpe: kapseldage tavalised kaardistatud tüüpide mustrid üldistesse utiliidi tüüpidesse. See soodustab järjepidevust ja vähendab katlakivi koodi kogu teie projektis ja globaalsete meeskondade jaoks.
- Kasutage kirjeldavaid nimesid: nimetage oma kaardistatud tüübid ja üldparameetrid selgelt, et näidata nende eesmärki (nt
Optional<T>,DeepReadonly<T>,PrefixedKeys<T, Prefix>). - Seadke prioriteediks loetavus: kui kaardistatud tüüp muutub liiga keeruliseks, kaaluge, kas on olemas lihtsam viis sama tulemuse saavutamiseks või kas see on lisatud keerukust väärt. Mõnikord on veidi verbaalsem, kuid selgem tüübi määratlus eelistatavam.
- Dokumenteerige keerukad tüübid: keerukate kaardistatud tüüpide puhul lisage JSDoc-i kommentaarid, mis selgitavad nende funktsionaalsust, eriti kui jagate koodi mitmekesises rahvusvahelises meeskonnas.
- Testige oma tüüpe: kirjutage tüübikatseid või kasutage näiteid, et veenduda, et teie kaardistatud tüübid käituvad ootuspäraselt. See on eriti oluline keerukate teisenduste puhul, kus varjatud vigu võib olla raske tuvastada.
Järeldus
TypeScripti kaardistatud tüübid on täiustatud tüüpide manipuleerimise nurgakivi, pakkudes arendajatele enneolematut jõudu objektitüüpide teisendamiseks ja kohandamiseks. Olenemata sellest, kas muudate omadused valikuliseks, ainult lugemiseks, nimetate neid ümber või filtreerite neid keeruliste tingimuste alusel, pakuvad kaardistatud tüübid deklaratiivset, tüübikindlat ja väga väljendusrikast viisi oma andmestruktuuride haldamiseks.
Neid tehnikaid valdades saate märkimisväärselt suurendada koodi taaskasutatavust, parandada tüübikindlust ning luua tugevamaid ja hooldatavamaid rakendusi. Võtke omaks kaardistatud tüüpide jõud, et tõsta oma TypeScripti arendust ja aidata kaasa kvaliteetsete tarkvaralahenduste loomisele ülemaailmsele publikule. Kuna teete koostööd arendajatega erinevatest piirkondadest, võivad need täiustatud tüübimustrid olla ühine keel koodi kvaliteedi ja järjepidevuse tagamiseks, ületades võimalikke suhtluslünki tüübisüsteemi ranguse kaudu.