Utili API React per la creazione di componenti flessibili con TypeScript
Pubblicato: 2022-03-10 Hai mai usato React.createElement
direttamente? Che dire di React.cloneElement
? React è molto più che trasformare il tuo JSX in HTML. Molto di più e per aiutarti a migliorare la tua conoscenza delle API meno conosciute (ma molto utili) con cui viene fornita la libreria React. Esamineremo alcuni di essi e alcuni dei loro casi d'uso che possono migliorare drasticamente l'integrazione e l'utilità dei componenti.
In questo articolo, esamineremo alcune utili API React che non sono così comunemente conosciute ma estremamente utili per gli sviluppatori web. I lettori dovrebbero avere esperienza con la sintassi di React e JSX, la conoscenza di Typescript è utile ma non necessaria. I lettori se ne andranno con tutto ciò che devono sapere per migliorare notevolmente i componenti di React quando li utilizzano nelle applicazioni React.
React.cloneElement
La maggior parte degli sviluppatori potrebbe non aver mai sentito parlare di cloneElement
o averlo mai usato. È stato introdotto relativamente di recente per sostituire la funzione cloneWithProps
, ora deprecata. cloneElement
clona un elemento, ti consente anche di unire nuovi oggetti di scena con l'elemento esistente, modificandoli o sovrascrivendoli come meglio credi. Ciò apre opzioni estremamente potenti per la creazione di API di livello mondiale per componenti funzionali. Dai un'occhiata alla firma.
function cloneElement( element, props?, ...children)
Ecco la versione ridotta di Typescript:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Puoi prendere un elemento, modificarlo, persino sovrascrivere i suoi figli e quindi restituirlo come nuovo elemento. Dai un'occhiata al seguente esempio. Diciamo che vogliamo creare un componente TabBar di collegamenti. Potrebbe sembrare qualcosa del genere.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
La TabBar è un elenco di collegamenti, ma abbiamo bisogno di un modo per definire due parti di dati, il titolo del collegamento e l'URL. Quindi vorremo passare una struttura dati con queste informazioni. Quindi il nostro sviluppatore renderebbe il nostro componente così.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
Questo è fantastico, ma cosa succede se l'utente desidera eseguire il rendering di elementi a
button
anziché elementi? Bene, potremmo aggiungere un'altra proprietà che dice al componente quale tipo di elemento renderizzare.
Ma puoi vedere come questo diventerà rapidamente ingombrante, avremmo bisogno di supportare sempre più proprietà per gestire vari casi d'uso e casi limite per la massima flessibilità.
Ecco un modo migliore, usando React.cloneElement
.
Inizieremo modificando la nostra interfaccia per fare riferimento al tipo ReactNode
. Questo è un tipo generico che comprende tutto ciò che React può eseguire il rendering, in genere JSX Elements ma può anche essere stringhe e persino null
. Questo è utile per designare l'utente a voler accettare i componenti React o JSX come argomenti inline.
export interface ITabbarProps { links: ReactNode[] }
Ora chiediamo all'utente di darci alcuni React Elements e li renderemo come vogliamo.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Questo è perfettamente valido e renderebbe i nostri elementi. Ma stiamo dimenticando un paio di cose. Per uno, key
! Vogliamo aggiungere chiavi in modo che React possa eseguire il rendering delle nostre liste in modo efficiente. Vogliamo anche modificare i nostri elementi per apportare le trasformazioni necessarie in modo che si adattino al nostro stile, come className
e così via.
Possiamo farlo con React.cloneElement
e un'altra funzione React.isValidElement
per verificare che l'argomento sia conforme a ciò che ci aspettiamo!
React.isValidElement
Questa funzione restituisce true
se un elemento è un elemento React valido e React può renderlo. Ecco un esempio di modifica degli elementi dell'esempio precedente.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Qui stiamo aggiungendo un elemento chiave per ogni elemento che stiamo passando e rendiamo ogni collegamento in grassetto allo stesso tempo! Ora possiamo accettare elementi di reazione arbitrari come oggetti di scena in questo modo:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Possiamo ignorare qualsiasi oggetto di scena impostato su un elemento e accettare facilmente vari tipi di elementi rendendo il nostro componente molto più flessibile e facile da usare.
“
Il vantaggio qui è che se volessimo impostare un gestore onClick
personalizzato sul nostro pulsante, potremmo farlo. Accettare gli stessi elementi React come argomenti è un modo efficace per dare flessibilità alla progettazione dei componenti.
useState
Setter Function
Usa i ganci! L'hook useState
è estremamente utile e una fantastica API per creare rapidamente lo stato nei tuoi componenti in questo modo:
const [myValue, setMyValue] = useState()
A causa del runtime JavaScript, può avere alcuni intoppi. Ricordi le chiusure?
In determinate situazioni, una variabile potrebbe non essere il valore corretto a causa del contesto in cui si trova, ad esempio nei cicli for comuni o negli eventi asincroni. Ciò è dovuto all'ambito lessicale. Quando viene creata una nuova funzione, l'ambito lessicale viene mantenuto. Poiché non esiste una nuova funzione, l'ambito lessicale di newVal
non viene mantenuto e quindi il valore viene effettivamente dereferenziato nel momento in cui viene utilizzato.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
Quello che devi fare è utilizzare il setter come funzione. Creando una nuova funzione, il riferimento alle variabili viene preservato nell'ambito lessicale e currentVal viene passato dallo stesso React useState Hook.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Ciò garantirà che il valore venga aggiornato correttamente perché la funzione setter viene chiamata nel contesto corretto. Ciò che React fa qui è chiamare la tua funzione nel contesto corretto affinché si verifichi un aggiornamento dello stato di React. Questo può essere utilizzato anche in altre situazioni in cui è utile agire sul valore corrente, React chiama la tua funzione con il primo argomento come valore corrente.
Nota : per ulteriori letture sull'argomento asincrono e chiusure, consiglio di leggere " useState
Lazy Initialization and Function Updates" di Kent C. Dodds.
Funzioni in linea JSX
Ecco una demo Codepen di una funzione inline JSX:
Non esattamente un'API React per dire.
JSX supporta le funzioni inline e può essere davvero utile per dichiarare una logica semplice con variabili inline, purché restituisca un elemento JSX.
“
Ecco un esempio:
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
Qui stiamo dichiarando il codice all'interno di JSX, possiamo eseguire codice arbitrario e tutto ciò che dobbiamo fare è restituire una funzione JSX da renderizzare.
Possiamo renderlo condizionale o semplicemente eseguire un po' di logica. Prendi nota delle parentesi che circondano la funzione inline. Anche in particolare qui dove stiamo chiamando questa funzione, potremmo anche passare un argomento in questa funzione dal contesto circostante se lo volessimo!
})()}
Questo può essere utile in situazioni in cui si desidera agire su una struttura di dati di raccolta in un modo più complesso rispetto a quanto consentito da un .map
standard all'interno di un elemento JSX.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Qui possiamo eseguire del codice per scorrere un insieme di numeri e quindi visualizzarli in linea. Se utilizzi un generatore di siti statici come Gatsby, anche questo passaggio sarebbe precalcolato.
component extends type
Estremamente utile per la creazione di componenti compatibili con il completamento automatico, questa funzione consente di creare componenti che estendono gli HTMLElements
esistenti o altri componenti. Per lo più utile per digitare correttamente un'interfaccia di elementi in Typescript, ma l'applicazione effettiva è la stessa per JavaScript.
Ecco un semplice esempio, supponiamo di voler sovrascrivere una o due proprietà di un elemento button
, ma dare comunque agli sviluppatori la possibilità di aggiungere altre proprietà al pulsante. Come l'impostazione type='button'
o type='submit'
. Ovviamente non vogliamo ricreare l'intero elemento del pulsante, vogliamo solo estendere le sue proprietà esistenti e magari aggiungere un altro oggetto di scena.
import React, { ButtonHTMLAttributes } from 'react'
Per prima cosa importiamo React e la classe ButtonHTMLAttributes
, un tipo che racchiude gli oggetti di scena di un HTMLButtonElement
. Puoi leggere il codice sorgente per questo tipo di interfaccia qui:
E puoi vedere che il team di React ha reimplementato tutte le API del Web in TypeScript, quindi può essere verificato il tipo.
Successivamente, dichiariamo la nostra interfaccia in questo modo, aggiungendo la nostra proprietà di status
.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
E infine, facciamo un paio di cose. Usiamo la destrutturazione ES6 per estrarre gli oggetti di scena a cui teniamo ( status
e children
) e dichiariamo qualsiasi altra proprietà come rest
. E nel nostro output JSX, restituiamo un elemento pulsante, con la struttura ES6 per aggiungere eventuali proprietà aggiuntive a questo elemento.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
Quindi ora uno sviluppatore può aggiungere un oggetto di type
o qualsiasi altro oggetto che un pulsante avrebbe in genere. Abbiamo fornito un supporto aggiuntivo che abbiamo utilizzato in className
per impostare lo stile del pulsante.
Ecco l'intero esempio:
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
Questo è un ottimo modo per creare componenti interni riutilizzabili conformi alle tue linee guida di stile senza ricostruire interi elementi HTML! Puoi semplicemente ignorare interi oggetti di scena come impostare il className
in base allo stato o consentire anche il passaggio di nomi di classi aggiuntivi.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
Qui prendiamo il prop className
passato al nostro elemento Button e lo reinseriamo, con un controllo di sicurezza nel caso in cui il prop sia undefined
.
Conclusione
React è una libreria estremamente potente e c'è una buona ragione per cui ha rapidamente guadagnato popolarità. Ti offre un ottimo set di strumenti per creare app Web performanti e di facile manutenzione. È estremamente flessibile e allo stesso tempo molto rigoroso, il che può essere incredibilmente utile se sai come usarlo. Queste sono solo alcune delle API degne di nota e in gran parte trascurate. Provali nel tuo prossimo progetto!
Per ulteriori informazioni sulle ultime API React, hook, consiglierei di leggere useHooks(). Il cheatsheet di Typescript ha anche alcune ottime informazioni per React e Typescript Hooks.