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')
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 = {}
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 KontextwertSo nutzen Sie React Context effektivVerwalten des Anwendungsstatus in React.Ein einfacher Trick, um Re-Renderings in React zu optimieren