Lucrul cu React Hooks și TypeScript
Publicat: 2022-03-11Cârligele au fost introduse în React în februarie 2019 pentru a îmbunătăți lizibilitatea codului. Am discutat deja despre cârligele React în articolele anterioare, dar de data aceasta examinăm modul în care funcționează cârligele cu TypeScript.
Înainte de cârlige, componentele React aveau două arome:
- Clase care se ocupă de un stat
- Funcții care sunt complet definite de recuzita lor
O utilizare naturală a acestora a fost de a construi componente complexe de containere cu clase și componente simple de prezentare cu funcții pure.
Ce sunt React Hooks?
Componentele containerului gestionează gestionarea stării și solicitările către server, care vor fi apoi numite în acest articol efecte secundare . Starea va fi propagată copiilor container prin recuzită.
Dar pe măsură ce codul crește, componentele funcționale tind să fie transformate ca componente container.
Actualizarea unei componente funcționale într-una mai inteligentă nu este atât de dureroasă, dar este o sarcină neplăcută și consumatoare de timp. Mai mult, distingerea foarte strictă a prezentatorilor și a containerelor nu mai este binevenită.
Cârligele pot face ambele, astfel încât codul rezultat este mai uniform și are aproape toate beneficiile. Iată un exemplu de adăugare a unui stat local la o componentă mică care gestionează o semnătură de citat.
// put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }
Există un mare bonus la acest lucru - codificarea cu TypeScript a fost grozavă cu Angular, dar umflată cu React. Cu toate acestea, codarea cârligelor React cu TypeScript este o experiență plăcută.
TypeScript cu Old React
TypeScript a fost proiectat de Microsoft și a urmat calea Angular atunci când React a dezvoltat Flow , care acum își pierde tracțiunea. Scrierea claselor React cu TypeScript naiv a fost destul de dureroasă, deoarece dezvoltatorii React au trebuit să tasteze atât elementele de props
, cât și state
chiar dacă multe taste erau aceleași.
Iată un obiect de domeniu simplu. Realizăm o aplicație de cotație , cu tip Quotation
, gestionată în unele componente brute cu stare și recuzită. Quotation
poate fi creată, iar starea asociată se poate schimba în semnată sau nu.
interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }
Dar imaginați-vă că QuotationPage va cere serverului un id : de exemplu, a 678-a cotație făcută de companie. Ei bine, asta înseamnă că QuotationProps nu cunoaște acel număr important – nu include exact o cotație . Trebuie să declarăm mult mai mult cod în interfața QuotationProps:
interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }
Copiem toate atributele, cu excepția id-ului, într-un tip nou. Hm. Asta mă face să mă gândesc la vechiul Java, care presupunea scrierea a mai multor DTO-uri. Pentru a depăși acest lucru, ne vom spori cunoștințele TypeScript pentru a ocoli durerea.
Beneficiile TypeScript cu cârlige
Folosind cârlige, vom putea scăpa de interfața anterioară QuotationState. Pentru a face acest lucru, vom împărți QuotationState în două părți diferite ale statului.
interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }
Prin împărțirea statului, nu trebuie să creăm noi interfețe. Tipurile de stare locale sunt adesea deduse din valorile implicite de stare.
Componentele cu cârlige sunt toate funcții. Deci, putem scrie aceeași componentă returnând tipul FC<P>
definit în biblioteca React. Funcția își declară în mod explicit tipul de returnare, setând de-a lungul tipului de recuzită.
const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }
Evident, utilizarea TypeScript cu cârlige React este mai ușoară decât utilizarea cu clase React. Și deoarece tastarea puternică este o securitate valoroasă pentru siguranța codului, ar trebui să luați în considerare utilizarea TypeScript dacă noul dvs. proiect folosește cârlige. Cu siguranță ar trebui să utilizați cârlige dacă doriți ceva TypeScript.
Există numeroase motive pentru care ați putea evita TypeScript, folosind React sau nu. Dar dacă alegi să-l folosești, cu siguranță ar trebui să folosești și cârlige.
Caracteristici specifice ale TypeScript potrivite pentru cârlige
În exemplul precedent cu React hooks TypeScript, încă mai am atributul număr în QuotationProps, dar încă nu există nicio idee despre ce este de fapt acel număr.
TypeScript ne oferă o listă lungă de tipuri de utilitare, iar trei dintre ele ne vor ajuta cu React reducând zgomotul multor descrieri de interfețe.
-
Partial<T>
: orice subchei ale lui T -
Omit<T, 'x'>
: toate cheile lui T, cu excepția tasteix
-
Pick<T, 'x', 'y', 'z'>
: exact tastelex, y, z
dinT
În cazul nostru, am dori Omit<Quotation, 'id'>
să omite id-ul citatului. Putem crea un tip nou din mers cu cuvântul cheie type
.
Partial<T>
și Omit<T>
nu există în majoritatea limbilor tipizate, cum ar fi Java, dar ajută foarte mult pentru exemple cu Forms în dezvoltarea front-end. Simplifică sarcina de tastare.
type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }
Acum avem o cotație fără ID. Deci, poate am putea proiecta o cotație și Quotation
PersistedQuotation
extends
Quotation
. De asemenea, vom rezolva cu ușurință unele probleme recurente if
sau undefined
. Ar trebui să numim în continuare cotația variabilă, deși nu este obiectul complet? Asta depășește scopul acestui articol, dar îl vom menționa mai târziu oricum.

Cu toate acestea, acum suntem siguri că nu vom răspândi un obiect despre care credeam că are un number
. Utilizarea Partial<T>
nu aduce toate aceste garanții, așa că utilizați-l cu discreție.
Pick<T, 'x'|'y'>
este o altă modalitate de a declara un tip din mers fără sarcina de a declara o nouă interfață. Dacă este o componentă, editați pur și simplu titlul citației:
type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>
Sau doar:
function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}
Nu mă judeca, sunt un mare fan al Domain Driven Design. Nu sunt leneș până la punctul în care nu vreau să mai scriu două rânduri pentru o nouă interfață. Folosesc interfețe pentru descrierea precisă a numelor de domenii și aceste funcții utilitare pentru corectitudinea codului local, evitând zgomotul. Cititorul va ști că Quotation
este interfața canonică.
Alte beneficii ale React Hooks
Echipa React a văzut și tratat întotdeauna React ca pe un cadru funcțional. Ei au folosit clase astfel încât o componentă să poată gestiona propria stare, iar acum cârlige ca tehnică care permite unei funcții să țină evidența stării componentei.
interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }
Iată un caz în care utilizarea Partial
este sigură și o alegere bună.
Deși o funcție poate fi executată de mai multe ori, cârligul useReducer
asociat va fi creat o singură dată.
Prin extragerea în mod natural a funcției reductorului din componentă, codul poate fi împărțit în mai multe funcții independente în loc de funcții multiple în interiorul unei clase, toate legate de starea din interiorul clasei.
Acest lucru este în mod clar mai bun pentru testabilitate - unele funcții se ocupă de JSX, altele de comportament, altele de logica de afaceri și așa mai departe.
Nu mai aveți (aproape) nevoie de componente de ordin superior. Modelul Render props este mai ușor de scris cu funcții.
Deci, citirea codului este mai ușoară. Codul dvs. nu este un flux de clase/funcții/modele, ci un flux de funcții. Cu toate acestea, deoarece funcțiile dvs. nu sunt atașate unui obiect, poate fi dificil să denumiți toate aceste funcții.
TypeScript este încă JavaScript
JavaScript este distractiv, deoarece vă puteți rupe codul în orice direcție. Cu TypeScript, puteți folosi în continuare keyof
pentru a vă juca cu tastele obiectelor. Puteți folosi uniuni de tip pentru a crea ceva de necitit și de neîntreținut - nu, nu-mi plac. Puteți utiliza tipul alias pentru a pretinde că un șir este un UUID.
Dar o poți face cu siguranță nulă. Asigurați-vă că tsconfig.json
are opțiunea "strict":true
. Verificați-l înainte de începerea proiectului, sau va trebui să refactorizați aproape fiecare linie!
Există dezbateri cu privire la nivelul de tastare pe care îl introduceți în cod. Puteți introduce totul sau lăsați compilatorul să deducă tipurile. Depinde de configurația linter și de alegerile echipei.
De asemenea, mai puteți face erori de rulare! TypeScript este mai simplu decât Java și evită problemele de covarianță/contravarianță cu Generics.
În acest exemplu de animale/pisici, avem o listă de animale care este identică cu lista de pisici. Din păcate, este un contract în prima linie, nu a doua. Apoi, adăugăm o rață la lista de animale, astfel încât lista de pisici este falsă.
interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }
TypeScript are doar o abordare bivariantă pentru generice, care este simplă și ajută la adoptarea dezvoltatorilor JavaScript. Dacă denumești corect variabilele, rareori vei adăuga o duck
la o listOfCats
.
De asemenea, există o propunere de adăugare a contractelor de intrare și ieșire pentru covarianță și contravarianță.
Concluzie
Recent m-am întors la Kotlin, care este un limbaj bun, tastat, și a fost complicat să tastați corect generice complexe. Nu aveți acea complexitate generică cu TypeScript din cauza simplității tastării duck și a abordării bivariante, dar aveți alte probleme. Poate că nu ați întâmpinat multe probleme cu Angular, conceput cu și pentru TypeScript, dar le-ați avut cu clasele React.
TypeScript a fost probabil marele câștigător al anului 2019. A câștigat React, dar cucerește și lumea back-end cu Node.js și capacitatea sa de a tasta biblioteci vechi cu fișiere de declarație destul de simple. Îl îngroapă pe Flow, deși unii merg până la capăt cu ReasonML.
Acum, simt că hooks+TypeScript este mai plăcut și mai productiv decât Angular. N-aș fi crezut asta acum șase luni.