Redux integrierte Alternative mit React Context und Hooks

Vom Übersetzer:

Ich präsentiere eine kostenlose Übersetzung eines Artikels darüber, wie eine effektive Lösung implementiert werden kann, um Redux durch React-Kontext und Hooks zu ersetzen. Die Angabe von Fehlern in der Übersetzung oder im Text ist willkommen. Viel Spaß beim Betrachten.



Seit der Veröffentlichung der neuen Kontext-API in React 16.3.0 haben sich viele Leute gefragt, ob die neue API gut genug ist, um sie als Ersatz für Redux zu betrachten. Ich habe das Gleiche gedacht, aber ich habe es auch nach der Veröffentlichung von Version 16.8.0 mit Hooks nicht vollständig verstanden. Ich versuche, gängige Technologien zu verwenden. Der Weg besteht nicht immer darin, die gesamte Bandbreite der Probleme zu verstehen, die sie lösen. Daher bin ich zu sehr an Redux gewöhnt.

Und so kam es, dass ich mich für den Newsletter von Kent C. Dodds anmeldete und einige E-Mails zum Thema Kontext- und Zustandsmanagement entdeckte. Ich fing an zu lesen .... und lesen ... und nach 5 Blog-Posts hat etwas geklickt.

Um alle grundlegenden Konzepte dahinter zu verstehen, erstellen wir eine Schaltfläche, indem wir darauf klicken, dass wir Witze mit icanhazdadjoke erhalten und sie anzeigen. Dies ist ein kleines, aber ausreichendes Beispiel.

Beginnen wir zur Vorbereitung mit zwei scheinbar zufälligen Tipps.

Lassen Sie mich zunächst meinen Freund console.count :

 console.count('Button') // Button: 1 console.count('Button') // Button: 2 console.count('App') // App: 1 console.count('Button') // Button: 3 

Wir werden jeder Komponente einen Aufruf von console.count hinzufügen, um zu sehen, wie oft sie gerendert wird. Ziemlich cool, oder?

Zweitens wird beim erneuten Rendern einer React-Komponente der als untergeordnete Element übergebene Inhalt nicht erneut gerendert.

 function Parent({ children }) { const [count, setCount] = React.useState(0) console.count('Parent') return ( <div> <button type="button" onClick={() => { setCount(count => count + 1) }}> Force re-render </button> {children} </div> ) } function Child() { console.count('Child') return <div /> } function App() { return ( <Parent> <Child /> </Parent> ) } 

Nach ein paar Klicks auf die Schaltfläche sollte der folgende Inhalt in der Konsole angezeigt werden:

 Parent: 1 Child: 1 Parent: 2 Parent: 3 Parent: 4 

Beachten Sie, dass dies ein häufig übersehener Weg ist, um die Leistung Ihrer Anwendung zu verbessern.

Nachdem wir fertig sind, erstellen wir das Grundgerüst unserer Anwendung:

 import React from 'react' function Button() { console.count('Button') return ( <button type="button"> Fetch dad joke </button> ) } function DadJoke() { console.count('DadJoke') return ( <p>Fetched dad joke</p> ) } function App() { console.count('App') return ( <div> <Button /> <DadJoke /> </div> ) } export default App 

Button sollte einen Aktionsgenerator erhalten (ca. Aktionsersteller. Die Übersetzung stammt aus der Redux-Dokumentation in russischer Sprache ), der einen Witz erhält. DadJoke sollte den Status erhalten und die App beide Komponenten im Provider-Kontext anzeigen.

Erstellen Sie nun eine benutzerdefinierte Komponente und nennen Sie sie DadJokeProvider , der den Status verwaltet und die DadJokeProvider Komponenten in den Kontextanbieter einbindet. Denken Sie daran, dass durch das Aktualisieren des Status aufgrund der oben genannten untergeordneten Optimierung in React nicht die gesamte Anwendung erneut gerendert wird.

Erstellen Sie also eine Datei und nennen Sie sie contexts/dad-joke.js :

 import React from 'react' const DadJokeContext = React.createContext() export function DadJokeContextProvider({ children }) { const state = { dadJoke: null } const actions = { fetchDadJoke: () => {}, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) } 

Wir exportieren auch 2 Hooks, um den Wert aus dem Kontext zu erhalten.

 export function useDadJokeState() { return React.useContext(DadJokeContext).state } export function useDadJokeActions() { return React.useContext(DadJokeContext).actions } 

Jetzt können wir dies implementieren:

 import React from 'react' import { DadJokeProvider, useDadJokeState, useDadJokeActions, } from './contexts/dad-joke' function Button() { const { fetchDadJoke } = useDadJokeActions() console.count('Button') return ( <button type="button" onClick={fetchDadJoke}> Fetch dad joke </button> ) } function DadJoke() { const { dadJoke } = useDadJokeState() console.count('DadJoke') return ( <p>{dadJoke}</p> ) } function App() { console.count('App') return ( <DadJokeProvider> <Button /> <DadJoke /> </DadJokeProvider> ) } export default App 

Hier! Dank der API haben wir die Hooks verwendet. Wir werden während des gesamten Beitrags keine Änderungen mehr an dieser Datei vornehmen.

Beginnen wir mit dem Hinzufügen von Funktionen zu unserer Kontextdatei, beginnend mit dem Status von DadJokeProvider . Ja, wir könnten einfach den useState Hook verwenden, aber lassen Sie uns stattdessen unseren Status durch reducer indem wir uns einfach die bekannte und beliebte Redux Funktionalität hinzufügen.

 function reducer(state, action) { switch (action.type) { case 'SET_DAD_JOKE': return { ...state, dadJoke: action.payload, } default: return new Error(); } } 

Jetzt können wir diesen Reduzierer an den useReducer Hook übergeben und mit der API Witze machen:

 export function DadJokeProvider({ children }) { const [state, dispatch] = React.useReducer(reducer, { dadJoke: null }) async function fetchDadJoke() { const response = await fetch('https://icanhazdadjoke.com', { headers: { accept: 'application/json', }, }) const data = await response.json() dispatch({ type: 'SET_DAD_JOKE', payload: data.joke, }) } const actions = { fetchDadJoke, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) } 

Sollte funktionieren! Klicken Sie auf die Schaltfläche, um Witze zu empfangen und anzuzeigen!

Lassen Sie uns die Konsole überprüfen:

 App: 1 Button: 1 DadJoke: 1 Button: 2 DadJoke: 2 Button: 3 DadJoke: 3 

Beide Komponenten werden jedes Mal neu gerendert, wenn der Status aktualisiert wird, aber nur eine von ihnen verwendet ihn tatsächlich. Stellen Sie sich eine reale Anwendung vor, in der Hunderte von Komponenten nur Aktionen verwenden. Wäre es schön, wenn wir all diese optionalen Renderings bereitstellen könnten?

Und hier betreten wir das Gebiet der relativen Gleichheit, also eine kleine Erinnerung:

 const obj = {} //       console.log(obj === obj) // true //        //  2   console.log({} === {}) // false 

Eine Komponente, die den Kontext verwendet, wird jedes Mal neu gerendert, wenn sich der Wert dieses Kontexts ändert. Schauen wir uns die Bedeutung unseres Kontextanbieters an:

 <DadJokeContext.Provider value={{ state, actions }}> 

Hier erstellen wir bei jedem Renderer ein neues Objekt. Dies ist jedoch unvermeidlich, da jedes Mal, wenn wir eine Aktion ( dispatch ) ausführen, ein neues Objekt erstellt wird. memoize ist es einfach unmöglich, diesen Wert zwischenzuspeichern ( memoize zu memoize ).

Und alles sieht aus wie das Ende der Geschichte, oder?

Wenn wir uns die Funktion fetchDadJoke , wird vom externen Bereich nur der dispatch , oder? Im Allgemeinen werde ich Ihnen ein kleines Geheimnis über die in useReducer und useState erstellten Funktionen useState . Der Kürze useState werde ich useState als Beispiel verwenden:

 let prevSetCount function Counter() { const [count, setCount] = React.useState() if (typeof prevSetCount !== 'undefined') { console.log(setCount === prevSetCount) } prevSetCount = setCount return ( <button type="button" onClick={() => { setCount(count => count + 1) }}> Increment </button> ) } 

Klicken Sie mehrmals auf die Schaltfläche und sehen Sie sich die Konsole an:

 true true true 

Sie werden feststellen, dass setCount für jedes Rendern dieselbe Funktion hat. Dies gilt auch für unsere dispatch .

Dies bedeutet, dass unsere Funktion fetchDadJoke von nichts abhängt, was sich im Laufe der Zeit ändert, und nicht von anderen Aktionsgeneratoren. fetchDadJoke das fetchDadJoke beim ersten Rendern nur einmal erstellt werden:

 const actions = React.useMemo(() => ({ fetchDadJoke, }), []) 

Können wir den Kontextwert optimieren, nachdem wir ein zwischengespeichertes Objekt mit Aktionen haben? Nein, denn egal wie gut wir das Wertobjekt optimieren, wir müssen aufgrund von Statusänderungen jedes Mal ein neues erstellen. Was ist jedoch, wenn wir ein Aktionsobjekt aus einem vorhandenen Kontext in einen neuen verschieben? Wer hat gesagt, dass wir nur einen Kontext haben können?

 const DadJokeStateContext = React.createContext() const DadJokeActionsContext = React.createContext() 

Wir können beide Kontexte in unserem DadJokeProvider :

  return ( <DadJokeStateContext.Provider value={state}> <DadJokeActionsContext.Provider value={actions}> {children} </DadJokeActionsContext.Provider> </DadJokeStateContext.Provider> ) 

Und optimieren Sie unsere Haken:

 export function useDadJokeState() { return React.useContext(DadJokeStateContext) } export function useDadJokeActions() { return React.useContext(DadJokeActionsContext) } 

Und wir sind fertig! Im Ernst, laden Sie so viele Witze herunter, wie Sie möchten, und überzeugen Sie sich selbst.

 App: 1 Button: 1 DadJoke: 1 DadJoke: 2 DadJoke: 3 DadJoke: 4 DadJoke: 5 

Sie haben also Ihre eigene optimierte Zustandsverwaltungslösung implementiert! Mit dieser Vorlage mit zwei Kontexten können Sie verschiedene Anbieter erstellen, um Ihre Anwendung zu erstellen. Das ist jedoch noch nicht alles. Sie können dieselbe Anbieterkomponente auch mehrmals rendern! Was ?! Ja, probieren Sie das DadJokeProvider Rendering an mehreren Stellen aus und sehen Sie, wie sich Ihre Implementierung der DadJokeProvider problemlos DadJokeProvider lässt!

Lassen Sie Ihrer Fantasie freien Lauf und überprüfen Sie, warum Sie Redux wirklich brauchen.

Vielen Dank an Kent C. Dodds für Artikel zur Zwei-Kontext-Vorlage. Ich habe ihn nirgendwo gesehen und es scheint mir, dass dies die Spielregeln ändert.

Lesen Sie die folgenden Kent-Blog-Beiträge, um weitere Informationen zu den Konzepten zu erhalten, über die ich gesprochen habe:

Wann verwenden Sie useMemo und useCallback?
So optimieren Sie den Kontextwert
So nutzen Sie React Context effektiv
Verwalten des Anwendungsstatus in React.
Ein einfacher Trick, um Re-Renderings in React zu optimieren

Source: https://habr.com/ru/post/de463607/


All Articles