
Mit der Veröffentlichung des neuen React 16.6.0 erschien HOOKS (VORSCHLAG) in der Dokumentation. Sie sind jetzt in React 17.0.0-Alpha verfügbar und werden im offenen RFC: React Hooks erläutert. Mal sehen, was es ist und warum es unter dem Schnitt benötigt wird.
Ja, es handelt sich um RFC, und Sie können die endgültige Implementierung beeinflussen, indem Sie mit den Erstellern von React diskutieren, warum sie diesen oder jenen Ansatz gewählt haben.
Schauen wir uns an, wie ein Standardhaken aussieht:
import { useState } from 'react'; function Example() {
Versuchen Sie, über diesen Code nachzudenken. Dies ist ein Teaser. Am Ende des Artikels werden Sie bereits verstehen, was er bedeutet. Das erste, was Sie wissen sollten, ist, dass dies die Abwärtskompatibilität nicht beeinträchtigt. Möglicherweise werden sie in 16.7 hinzugefügt, nachdem Sie Feedback und Vorschläge im RFC gesammelt haben.
Wie die Jungs versichern, ist dies kein Plan, um Klassen aus einem Reagenz herauszuschneiden.
Hooks ersetzen auch nicht die aktuellen Konzepte der Reaktion, alles ist anstelle von Requisiten / Zustand / Kontext / Refs. Dies ist nur ein weiterer Weg, um ihre Kraft zu nutzen.
Motivation
Hooks lösen auf den ersten Blick nicht verbundene Probleme, die mit der Unterstützung von Zehntausenden von Komponenten über einen Zeitraum von 5 Jahren von Facebook aufgetreten sind.
Am schwierigsten ist es, die Logik in statusbehafteten Komponenten wiederzuverwenden. Die Reaktion hat keine Möglichkeit, der Komponente wiederverwendbares Verhalten zuzuordnen (z. B. eine Verbindung zum Repository herzustellen). Wenn Sie mit React gearbeitet haben, kennen Sie das Konzept von HOC (High-Order-Component) oder Render-Requisiten. Diese Muster sind gut genug, aber manchmal werden sie übermäßig verwendet. Sie erfordern eine Umstrukturierung der Komponenten, damit sie verwendet werden können, was den Code normalerweise umständlicher macht. Es lohnt sich, sich eine typische Reaktionsanwendung anzusehen, und es wird klar, worum es geht.

Dies nennt man Wrap-Hell - Wrapper-Hölle.
Eine Anwendung von HOCs allein ist in der aktuellen Realität normal. Sie haben die Komponente mit dem Store / Theme / Localization / Custom Hock verbunden. Ich denke, das weiß jeder.
Es wird klar, dass die Reaktion einen anderen primitiven Mechanismus benötigt, um die Logik zu trennen.
Mithilfe von Hooks können wir den Status einer Komponente extrahieren, damit sie getestet und wiederverwendet werden kann. Mit Hooks können Sie die Statuslogik wiederverwenden, ohne die Komponentenhierarchie zu ändern. Dies erleichtert den Austausch von Verbindungen zwischen vielen Komponenten oder dem gesamten System. Außerdem sehen Klassenkomponenten ziemlich beängstigend aus. Wir beschreiben die Lebenszyklusmethoden von componentDidMount
/ shouldComponentUpdate
/ componentDidUpdate
, den Status der Komponente, erstellen Methoden für die Arbeit mit state / side, binden Methoden für die Komponenteninstanz und können so weiter und weiter gehen. Typischerweise gehen solche Komponenten über x Zeilen hinaus, wobei x schwer genug zu verstehen ist.
Mit Hooks können Sie dasselbe tun, indem Sie die Logik zwischen Komponenten in kleine Funktionen aufteilen und diese in Komponenten verwenden.
Der Unterricht ist schwierig für Menschen und für Autos
Beim Beobachten von Facebook sind Klassen ein großes Hindernis beim Lernen von React. Sie müssen verstehen, wie this
funktioniert, und es funktioniert nicht wie in anderen Programmiersprachen. Sie sollten sich auch an das Binden von Ereignishandlern erinnern. Ohne stabile Syntaxsätze sieht der Code sehr ausführlich aus. Die Leute verstehen die Requisiten / Zustandsmuster und den sogenannten Top-Down-Datenfluss, aber die Klassen sind ziemlich schwer zu verstehen.
Vor allem, wenn nicht auf Vorlagen beschränkt, experimentierten die Reaktionsteilnehmer vor nicht allzu langer Zeit mit dem Layout von Komponenten mit Prepack und sahen vielversprechende Ergebnisse. Trotzdem können Sie mit den Komponenten der Klasse unbeabsichtigte fehlerhafte Muster erstellen, die diese Optimierungen verschwinden lassen. Klassen migrieren auch nicht sehr gut, wenn Hot-Reload-Klassen machen es unzuverlässig. Zunächst wollten die Jungs eine API bereitstellen, die alle Optimierungen unterstützt und bei einem heißen Neustart einwandfrei funktioniert.
Schauen Sie sich die Haken an
Staatshaken
Der folgende Code rendert einen Absatz und eine Schaltfläche. Wenn wir auf die Schaltfläche klicken, wird der Wert im Absatz erhöht.
import { useState } from 'react'; function Example() {
Daraus können wir schließen, dass dieser Haken mit einem solchen Konzept wie state
ähnlich funktioniert.
Eine etwas detailliertere useState
Methode verwendet ein Argument. Dies ist der Standardwert und gibt ein Tupel zurück, in dem sich der Wert selbst und die Methode zum Ändern befinden. Im Gegensatz zu setState führt setCount keine Werte zusammen, sondern aktualisiert sie einfach. Wir können auch mehrere Zustandsdeklarationen verwenden, zum Beispiel:
function ExampleWithManyStates() {
Auf diese Weise erstellen wir mehrere Zustände gleichzeitig und müssen nicht darüber nachdenken, wie wir sie irgendwie zerlegen können. Daher kann unterschieden werden, dass Hooks Funktionen sind, mit denen Sie eine Verbindung zu den Chips von Klassenkomponenten herstellen können, so wie Hooks innerhalb von Klassen nicht funktionieren. Dies ist wichtig zu beachten.
Effekthaken
In Klassenkomponenten führen wir häufig Nebenwirkungsfunktionen aus, z. B. abonnieren Ereignisse oder stellen Datenanforderungen. In der Regel verwenden wir hierfür die Methoden componentDidMount
/ componentDidUpdate
import { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0);
Wenn wir useEffect
aufrufen, useEffect
wir die Reaktion an, nach dem Aktualisieren der Änderungen im DOM-Baum einen Nebeneffekt useEffect
. Effekte werden innerhalb der Komponente deklariert, daher haben sie Zugriff auf Requisiten / Status. Darüber hinaus können wir sie genauso erstellen, wie Sie möchten.
function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); function handleStatusChange(status) { setIsOnline(status.isOnline); }
Sofort lohnt es sich, auf den zweiten Nebeneffekt zu achten. Wir geben die Funktion zurück. Wir tun dies, um einige Aktionen auszuführen, nachdem die Komponente die Bereitstellung aufgehoben hat. In der neuen API wird dies als Effekte mit Reinigung bezeichnet. Andere Effekte können alles zurückgeben.
Hakenregeln
Hooks sind nur Javascript-Funktionen, erfordern jedoch nur zwei Regeln:
- Hooks sollten ganz oben in der Funktionshierarchie ausgeführt werden (dies bedeutet, dass Sie Hooks nicht in Bedingungen und Schleifen aufrufen sollten, da die Reaktion sonst die Ausführungsreihenfolge von Hooks nicht garantieren kann).
- Rufen Sie Hooks nur in React-Funktionen oder Funktionskomponenten auf oder rufen Sie Hooks von benutzerdefinierten Hooks auf (siehe unten).
Um diese Regeln zu befolgen, haben die Mitarbeiter des Reaktionsteams ein Linter-Plugin erstellt , das einen Fehler auslöst , wenn Sie Hooks in Klassenkomponenten oder in Schleifen und Bedingungen aufrufen.
Benutzerdefinierte Haken
Gleichzeitig möchten wir die Logik zustandsbehafteter Komponenten wiederverwenden. In der Regel werden hierfür entweder HOC- oder Render-Requisitenmuster verwendet, die jedoch zusätzliches Volumen unserer Anwendung erzeugen.
Zum Beispiel beschreiben wir die folgende Funktion:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
Realisieren Sie diesen Code, es wird ein benutzerdefinierter Hook sein, den wir in verschiedenen Komponenten aufrufen können. Zum Beispiel so:
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
oder so
function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
In jedem Fall verwenden wir den Status der Komponente wieder. Jeder Aufruf der Funktion useFriendStatus
erstellt einen isolierten Status. Es ist auch erwähnenswert, dass der Beginn dieser Funktion mit dem Wort use beginnt, was bedeutet, dass es sich um einen Hook handelt. Wir empfehlen, dass Sie diesem Format folgen. Sie können benutzerdefinierte Hooks für alles, Animationen / Abonnements / Timer und vieles mehr schreiben.
Es gibt noch ein paar Haken.
useContext
useContext
können Sie anstelle von renderProps den üblichen Rückgabewert verwenden, den Kontext, den wir abrufen möchten, und er wird an uns zurückgegeben, sodass wir alle HOCs entfernen können, die den Kontext an Requisiten übergeben haben.
function Example() { const locale = useContext(LocaleContext); const theme = useContext(ThemeContext);
Und jetzt können wir einfach das Kontextobjekt im Rückgabewert verwenden.
useCallback
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
Wie oft mussten Sie eine Komponente einer Klasse erstellen, um einen Verweis auf eine Methode zu speichern? Dies muss nicht mehr durchgeführt werden, wir können useCallback verwenden und unsere Komponenten werden nicht neu gezeichnet, da ein neuer Link zu onClick eingetroffen ist.
useMemo
Wir geben den gespeicherten Wert zurück. Der gespeicherte Wert bedeutet, dass er nur berechnet wird, wenn sich eines der Argumente geändert hat. Beim zweiten Mal wird dasselbe nicht berechnet.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Ja, hier müssen Sie die Werte im Array duplizieren, damit der Hook versteht, dass sie sich nicht geändert haben.
useRef
useRef
gibt einen mutierten Wert zurück, bei dem .current
Feld .current
mit dem ersten Argument initialisiert wird. Das Objekt bleibt bestehen, solange die Komponente vorhanden ist.
Das häufigste Beispiel, wenn Sie sich auf die Eingabe konzentrieren
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => {
useImperativeMethods
useImperativeMethods
den Wert der Instanz an, die vom übergeordneten useImperativeMethods
wird, und verwendet ref direkt. Wie immer sollten direkte Links vermieden und forwardRef
verwendet werden
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeMethods(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
In diesem Beispiel kann die Komponente, FancyInput
, fancyInputRef.current.focus()
aufrufen.
useMutationEffect
useMutationEffect
sehr ähnlich, außer dass es synchron in dem Stadium startet, in dem die Reaktion die DOM-Werte ändert, bevor benachbarte Komponenten aktualisiert werden. Dieser Hook sollte verwendet werden, um DOM-Mutationen durchzuführen.
Es ist besser, useEffect zu bevorzugen, um das Blockieren visueller Änderungen zu verhindern.
useLayoutEffect
useLayoutEffect
ähnelt useEffect
Ausnahme, dass es nach allen DOM-Aktualisierungen und dem synchronen erneuten Rendern synchron ausgeführt wird. In useLayoutEffect
geplante useLayoutEffect
werden synchron angewendet, bevor der Browser Elemente zeichnen kann. Sie sollten auch versuchen, den Standard useEffect
zu verwenden, um visuelle Änderungen nicht zu blockieren.
useReducer
useReducer
ist ein Hook zum Erstellen eines Reduzierers, der den Status und die Fähigkeit zum Versenden von Änderungen zurückgibt:
const [state, dispatch] = useReducer(reducer, initialState);
Wenn Sie verstehen, wie Redux funktioniert, verstehen Sie, wie useReducer
funktioniert. Das gleiche Beispiel, das mit dem obigen Zähler nur über useReducer
:
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return initialState; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
UseReducer akzeptiert auch 3 Argumente. Dies ist die action
, die ausgeführt werden soll, wenn der Reduzierer initialisiert wird:
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return {count: action.payload}; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer( reducer, initialState, {type: 'reset', payload: initialCount}, ); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
Wir können in diesem Reduzierer auch einen Kontext erstellen und ihn über den useContext
Hook verwenden useContext
um ihn in der gesamten Anwendung zu verwenden. Dies bleibt für Hausaufgaben.
Zusammenfassend
Hooks sind ein ziemlich leistungsfähiger Ansatz zur Lösung der Wrapper-Hölle und zur Lösung mehrerer Probleme, aber alle können mit derselben Definition der Verbindungsübertragung verwendet werden . Bereits jetzt erscheinen Sammlungen von Haken zur Verwendung oder diese Sammlung . Weitere Informationen zu Hooks finden Sie in der Dokumentation .