Nützliche React-APIs zum Erstellen flexibler Komponenten mit TypeScript
Veröffentlicht: 2022-03-10 Haben Sie React.createElement
jemals direkt verwendet? Was ist mit React.cloneElement
? React ist mehr als nur die Umwandlung Ihres JSX in HTML. Viel mehr und um Ihnen zu helfen, Ihr Wissen über weniger bekannte (aber sehr nützliche) APIs zu verbessern, wird die React-Bibliothek mitgeliefert. Wir werden einige von ihnen und einige ihrer Anwendungsfälle durchgehen, die die Integration und Nützlichkeit Ihrer Komponenten drastisch verbessern können.
In diesem Artikel gehen wir auf einige nützliche React-APIs ein, die nicht so allgemein bekannt, aber für Webentwickler äußerst nützlich sind. Leser sollten Erfahrung mit React und JSX-Syntax haben, Typoskript-Kenntnisse sind hilfreich, aber nicht erforderlich. Die Leser erhalten alles, was sie wissen müssen, um die React-Komponenten bei der Verwendung in React-Anwendungen erheblich zu verbessern.
React.cloneElement
Die meisten Entwickler haben vielleicht noch nie von cloneElement
gehört oder es jemals verwendet. Sie wurde vor relativ kurzer Zeit eingeführt, um die inzwischen veraltete Funktion cloneWithProps
zu ersetzen. cloneElement
ein Element, es ermöglicht Ihnen auch, neue Requisiten mit dem bestehenden Element zusammenzuführen, sie zu modifizieren oder zu überschreiben, wie Sie es für richtig halten. Dies eröffnet äußerst leistungsstarke Optionen zum Erstellen erstklassiger APIs für funktionale Komponenten. Sehen Sie sich die Signatur an.
function cloneElement( element, props?, ...children)
Hier ist die komprimierte Typescript-Version:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Sie können ein Element nehmen, es ändern, sogar seine untergeordneten Elemente überschreiben und es dann als neues Element zurückgeben. Sehen Sie sich das folgende Beispiel an. Angenommen, wir möchten eine TabBar- Komponente mit Links erstellen. Das könnte in etwa so aussehen.
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> )} </> ) }
Die TabBar ist eine Liste von Links, aber wir brauchen eine Möglichkeit, zwei Datenelemente zu definieren, den Titel des Links und die URL. Wir möchten also, dass eine Datenstruktur mit diesen Informationen übergeben wird. Unser Entwickler würde unsere Komponente also so machen.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
Das ist großartig, aber was ist, wenn der Benutzer button
anstelle a
Elementen rendern möchte? Nun, wir könnten eine weitere Eigenschaft hinzufügen, die der Komponente mitteilt, welche Art von Element gerendert werden soll.
Aber Sie können sehen, wie dies schnell unhandlich wird, wir müssten immer mehr Eigenschaften unterstützen, um verschiedene Anwendungsfälle und Grenzfälle für maximale Flexibilität zu bewältigen.
Hier ist ein besserer Weg mit React.cloneElement
.
Wir beginnen damit, unsere Schnittstelle so zu ändern, dass sie auf den ReactNode
Typ verweist. Dies ist ein generischer Typ, der alles umfasst, was React rendern kann, typischerweise JSX-Elemente, aber auch Strings und sogar null
. Dies ist nützlich, um festzulegen, dass Sie React-Komponenten oder JSX als Argumente inline akzeptieren möchten.
export interface ITabbarProps { links: ReactNode[] }
Jetzt bitten wir den Benutzer, uns einige React-Elemente zu geben, und wir rendern sie so, wie wir es wollen.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Dies ist vollkommen gültig und würde unsere Elemente wiedergeben. Aber wir vergessen ein paar Dinge. Zum einen key
! Wir möchten Schlüssel hinzufügen, damit React unsere Listen effizient rendern kann. Wir möchten auch unsere Elemente ändern, um notwendige Transformationen vorzunehmen, damit sie in unser Styling passen, wie z. B. className
und so weiter.
Wir können dies mit React.cloneElement
und einer anderen Funktion React.isValidElement
tun, um zu überprüfen, ob das Argument unseren Erwartungen entspricht!
React.isValidElement
Diese Funktion gibt true
zurück, wenn ein Element ein gültiges React-Element ist und React es rendern kann. Hier ist ein Beispiel für die Änderung der Elemente aus dem vorherigen Beispiel.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Hier fügen wir jedem Element, das wir übergeben, eine Schlüsselstütze hinzu und machen gleichzeitig jeden Link fett! Wir können jetzt beliebige React-Elemente wie folgt als Requisiten akzeptieren:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Wir können alle Requisiten überschreiben, die für ein Element festgelegt sind, und problemlos verschiedene Arten von Elementen akzeptieren, wodurch unsere Komponente viel flexibler und benutzerfreundlicher wird.
„
Der Vorteil hier ist, dass wir, wenn wir einen benutzerdefinierten onClick
Handler für unsere Schaltfläche festlegen wollten, dies tun könnten. Das Akzeptieren von React-Elementen selbst als Argumente ist eine leistungsstarke Möglichkeit, Ihrem Komponentendesign Flexibilität zu verleihen.
useState
Setter-Funktion
Haken verwenden! Der useState
Hook ist äußerst nützlich und eine fantastische API, um Status schnell in Ihre Komponenten einzubauen:
const [myValue, setMyValue] = useState()
Aufgrund der JavaScript-Laufzeit kann es zu Schluckauf kommen. Erinnern Sie sich an Schließungen?
In bestimmten Situationen hat eine Variable aufgrund des Kontexts, in dem sie sich befindet, möglicherweise nicht den richtigen Wert, z. B. in häufigen for-Schleifen oder asynchronen Ereignissen. Dies liegt am lexikalischen Scoping. Wenn eine neue Funktion erstellt wird, bleibt der lexikalische Gültigkeitsbereich erhalten. Da es keine neue Funktion gibt, bleibt der lexikalische Gültigkeitsbereich von newVal
nicht erhalten, und der Wert wird daher tatsächlich dereferenziert, wenn er verwendet wird.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
Was Sie tun müssen, ist den Setter als Funktion zu verwenden. Durch das Erstellen einer neuen Funktion wird die Variablenreferenz im lexikalischen Gültigkeitsbereich beibehalten und der currentVal wird vom React useState Hook selbst übergeben.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Dadurch wird sichergestellt, dass Ihr Wert korrekt aktualisiert wird, da die Setter-Funktion im richtigen Kontext aufgerufen wird. Was React hier tut, ist, Ihre Funktion im richtigen Kontext aufzurufen, damit eine React-Statusaktualisierung erfolgt. Dies kann auch in anderen Situationen verwendet werden, in denen es hilfreich ist, auf den aktuellen Wert zu reagieren. React ruft Ihre Funktion mit dem ersten Argument als aktuellem Wert auf.
Hinweis : Für zusätzliche Lektüre zum Thema Async und Closures empfehle ich die Lektüre von „ useState
Lazy Initialization And Function Updates“ von Kent C. Dodds.
JSX-Inline-Funktionen
Hier ist eine Codepen-Demo einer JSX-Inline-Funktion:
Nicht gerade eine React-API pro-sagen.
JSX unterstützt Inline-Funktionen und kann sehr nützlich sein, um einfache Logik mit Inline-Variablen zu deklarieren, solange es ein JSX-Element zurückgibt.
„
Hier ist ein Beispiel:
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! </> ) }
Hier deklarieren wir Code innerhalb von JSX, wir können beliebigen Code ausführen und alles, was wir tun müssen, ist eine zu rendernde JSX-Funktion zurückzugeben.
Wir können es bedingt machen oder einfach etwas Logik ausführen. Beachten Sie die Klammern um die Inline-Funktion. Auch hier, wo wir diese Funktion aufrufen, könnten wir sogar ein Argument aus dem umgebenden Kontext an diese Funktion übergeben, wenn wir wollten!
})()}
Dies kann in Situationen nützlich sein, in denen Sie auf eine komplexere Art und Weise auf eine Sammlungsdatenstruktur einwirken möchten, als es eine standardmäßige .map
innerhalb eines JSX-Elements zulässt.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Hier können wir Code ausführen, um eine Reihe von Zahlen zu durchlaufen und sie dann inline anzuzeigen. Wenn Sie einen statischen Site-Generator wie Gatsby verwenden, wird dieser Schritt ebenfalls vorberechnet.
component extends type
Diese Funktion ist äußerst nützlich für die Erstellung von Komponenten, die für die automatische Vervollständigung geeignet sind, und ermöglicht Ihnen die Erstellung von Komponenten, die vorhandene HTMLElements
oder andere Komponenten erweitern. Meistens nützlich für die korrekte Eingabe einer Elementschnittstelle in Typescript, aber die eigentliche Anwendung ist für JavaScript dieselbe.
Hier ist ein einfaches Beispiel: Nehmen wir an, wir möchten eine oder zwei Eigenschaften eines button
überschreiben, Entwicklern aber dennoch die Möglichkeit geben, der Schaltfläche andere Eigenschaften hinzuzufügen. Wie die Einstellung type='button'
oder type='submit'
. Wir wollen natürlich nicht das gesamte Button-Element neu erstellen, wir wollen nur seine bestehenden Eigenschaften erweitern und vielleicht eine weitere Requisite hinzufügen.
import React, { ButtonHTMLAttributes } from 'react'
Zuerst importieren wir React und die Klasse ButtonHTMLAttributes
, einen Typ, der die Props eines HTMLButtonElement
. Sie können den Quellcode für diese Art von Schnittstelle hier lesen:
Und Sie können sehen, dass das React-Team alle APIs des Webs in TypeScript neu implementiert hat, damit sie typgeprüft werden können.
Als nächstes deklarieren wir unsere Schnittstelle so und fügen unsere status
-Eigenschaft hinzu.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
Und schließlich tun wir ein paar Dinge. Wir verwenden die ES6-Destrukturierung, um die Requisiten herauszuziehen, die uns wichtig sind ( status
und children
), und deklarieren alle anderen Eigenschaften als rest
. Und in unserer JSX-Ausgabe geben wir ein Schaltflächenelement mit ES6-Strukturierung zurück, um diesem Element zusätzliche Eigenschaften hinzuzufügen.
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> ) }
Jetzt kann ein Entwickler also eine type
oder jede andere Stütze hinzufügen, die eine Schaltfläche normalerweise haben würde. Wir haben eine zusätzliche Eigenschaft angegeben, die wir in className
, um den Stil der Schaltfläche festzulegen.
Hier ist das gesamte Beispiel:
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> ) }
Dies ist eine großartige Möglichkeit, wiederverwendbare interne Komponenten zu erstellen, die Ihren Stilrichtlinien entsprechen, ohne ganze HTML-Elemente neu erstellen zu müssen! Sie können einfach ganze Requisiten überschreiben, z. B. das Festlegen des className
basierend auf dem Status, oder auch die Übergabe zusätzlicher Klassennamen zulassen.
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> ) }
Hier nehmen wir den an unser Button-Element übergebenen Prop className
und fügen ihn wieder ein, mit einer Sicherheitsprüfung für den Fall, dass der Prop nicht undefined
ist.
Fazit
React ist eine extrem leistungsstarke Bibliothek, und es gibt einen guten Grund, warum sie schnell an Popularität gewonnen hat. Es bietet Ihnen ein großartiges Toolset, um leistungsstarke und einfach zu wartende Web-Apps zu erstellen. Es ist extrem flexibel und gleichzeitig sehr streng, was unglaublich nützlich sein kann, wenn Sie wissen, wie man es benutzt. Dies sind nur einige APIs, die bemerkenswert sind und weitgehend übersehen werden. Probieren Sie sie in Ihrem nächsten Projekt aus!
Für weitere Lektüre über die neuesten React-APIs, Hooks, würde ich empfehlen, useHooks() zu lesen. Das Typescript Cheatsheet enthält auch einige großartige Informationen für React- und Typescript-Hooks.