Hur man kommer igång med Redux för JavaScript State Management

0
219

Redux är ett tillståndshanteringsverktyg, byggt specifikt för JavaScript-applikationer på klientsidan som är starkt beroende av komplexa data och externa API:er, och ger fantastiska utvecklarverktyg som gör det enkelt att arbeta med dina data.

Vad gör Redux?< /h2>

Enkelt uttryckt är Redux ett centraliserat datalager. All din applikationsdata lagras i ett stort objekt. Redux Devtools gör det här enkelt att visualisera:

Detta tillstånd är oföränderligt, vilket är ett konstigt koncept till en början, men är vettigt av några skäl. Om du vill ändra tillståndet måste du skicka ut en åtgärd, som i princip tar några argument, bildar en nyttolast och skickar den över till Redux. Redux skickar det nuvarande tillståndet till en reducerfunktion, som modifierar det befintliga tillståndet och returnerar ett nytt tillstånd som ersätter det nuvarande och utlöser en omladdning av de berörda komponenterna. Till exempel kan du ha en reducering för att lägga till ett nytt objekt i en lista, eller ta bort eller redigera ett som redan finns.

Om du gör det på detta sätt kommer du aldrig att få något odefinierat beteende med ditt app-modifierande tillstånd efter behag. Dessutom, eftersom det finns register över varje åtgärd och vad den ändrade, möjliggör den felsökning av tidsresor, där du kan rulla tillbaka ditt programtillstånd för att felsöka vad som händer med varje åtgärd (ungefär som en git-historik).

Redux kan användas med vilket ramverk som helst, men det används ofta med React, och det är vad vi kommer att fokusera på här. Under huven använder Redux React’s Context API, som fungerar på samma sätt som Redux och är bra för enkla appar om du vill avstå från Redux helt. Men Redux’s Devtools är fantastiska när man arbetar med komplexa data, och de är faktiskt mer optimerade för att förhindra onödiga återgivningar.

Annons

Om du använder TypeScript, saker och ting är mycket mer komplicerade att få Redux strikt maskinskriven. Du vill istället följa den här guiden, som använder typesafe-actions för att hantera åtgärderna och reducerarna på ett typvänligt sätt.

Strukturering Ditt projekt

Först vill du lägga upp din mappstruktur. Detta är upp till dig och ditt teams stilpreferenser, men det finns i princip två huvudmönster som de flesta Redux-projekt använder. Den första är helt enkelt att dela upp varje typ av fil (åtgärd, reducering, mellanprogram, sidoeffekt) i sin egen mapp, som så:

store/actions/reducers/sagas/middleware/index.js

Detta är dock inte det bästa, eftersom du ofta behöver både en åtgärds- och reduceringsfil för varje funktion du lägger till. Det är bättre att slå samman mapparna för åtgärder och reduceringar och dela upp dem efter funktion. På så sätt finns varje åtgärd och motsvarande reducering i samma fil. Du

lagrar/features/todo/etc/sagas/middleware/root-reducer.js root-action.js index.js

Detta rensar upp importerna, eftersom du nu kan importera både åtgärderna och reducerarna i samma uttalande med:

importera { todosActions, todosReducer } från 'butik/funktioner/todos'

Det är upp till dig om du vill behålla Redux-koden i sin egen mapp (/lagra i exemplen ovan), eller integrera den i din apps root-mapp. Om du redan separerar kod per komponent och skriver många anpassade åtgärder och reducerare för varje komponent, kanske du vill slå samman mapparna /features/ och /components/ och lagra JSX-komponenter tillsammans med reduceringskoden.< /p>

Om du använder Redux med TypeScript kan du lägga till ytterligare en fil i varje funktionsmapp för att definiera dina typer.

Installera och konfigurera Redux

Installera Redux och React-Redux från NPM:

npm installera redux react-redux

Du kommer förmodligen också att vilja ha redux-devtools:

npm install –save-dev redux-devtools

Det första du vill skapa är din butik. Spara detta som /store/index.js

import { createStore } från 'redux' import rootReducer från './root-reducer' const store = createStore(rootReducer) export standardlager; Annons

Naturligtvis kommer din butik att bli mer komplicerad än så här när du lägger till saker som sidoeffekttillägg, mellanprogram och andra verktyg som ansluten-reagera-router, men det här är allt som krävs för nu. Den här filen tar root-reduceraren och anropar createStore() med den, som exporteras för appen att använda.

Närnäst skapar vi en enkel att-göra-lista-funktion. Du kommer förmodligen att vilja börja med att definiera de åtgärder som den här funktionen kräver och argumenten som skickas till dem. Skapa en /features/todos/ mapp och spara följande som types.js:

export const ADD = 'ADD_TODO' export const DELETE = 'DELETE_TODO' export const EDIT = 'EDIT_TODO'

Detta definierar några strängkonstanter för åtgärdsnamnen. Oavsett vilken data du skickar runt kommer varje åtgärd att ha en typ egenskap, vilket är en unik sträng som identifierar åtgärden.

Du är inte skyldig att ha en typfil som denna, eftersom du bara kan skriva ut strängnamnet på åtgärden, men det är bättre för interoperabilitet att göra det på detta sätt. Du kan till exempel ha todos.ADD och reminders.ADD i samma app, vilket sparar dig besväret med att skriva _TODO eller _REMINDER varje gång du refererar till en åtgärd för den funktionen.

Spara sedan följande som /store/features/todos/actions.js:

import * som typer från './types.js' export const addTodo = text => ({ typ: types.ADD, text }) export const deleteTodo = id => ({ typ: types.DELETE, id }) export const editTodo = (id, text) => ({ typ: types.EDIT, id, text })

Detta definierar några åtgärder med hjälp av typerna från strängkonstanterna, och lägger ut argumenten och skapar nyttolast för var och en. Dessa behöver inte vara helt statiska, eftersom de är funktioner—ett exempel som du kan använda är att ställa in en runtime CUID för vissa åtgärder.

Annons

Den mest komplicerade biten av kod, och där du kommer att implementera det mesta av din affärslogik, finns i reducerarna. Dessa kan ta många former, men den vanligaste inställningen är med en switch-sats som hanterar varje fall baserat på åtgärdstypen. Spara detta som reducer.js:

importera * som typer från './types.js' const initialState = [ { text: 'Hello World', id: 0 } ] exportera standardfunktion todos(state = initialState, action) { switch (action.type) { case types .ADD: returnera [ …tillstånd, { id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, text: action.text } ] skiftläge types.DELETE: return state.filter(todo => todo.id !== action.id ) case types.EDIT: return state.map(todo => todo.id === action.id ? { .. .todo, text: action.text } : todo ) default: return state } }

Tillståndet skickas som ett argument och varje fall returnerar en modifierad version av tillståndet. I det här exemplet lägger ADD_TODO till ett nytt objekt till staten (med ett nytt ID varje gång), DELETE_TODO tar bort alla objekt med det angivna ID:t och EDIT_TODO mappar och ersätter texten för objektet med det angivna ID:t.

< p>Det initiala tillståndet bör också definieras och skickas till reducerfunktionen som standardvärde för tillståndsvariabeln. Naturligtvis definierar detta inte hela Redux-tillståndsstrukturen, bara avsnittet state.todos.

Dessa tre filer är vanligtvis åtskilda i mer komplexa appar, men om du vill kan du också definiera dem alla i en fil, se bara till att du importerar och exporterar korrekt.

När den funktionen är klar, låt oss ansluta den till Redux (och till vår app). I /store/root-reducer.js importerar du todosReducer (och alla andra funktionsreducerare från mappen /features/) och skickar den sedan till combineReducers(), vilket bildar en rotreducerare på toppnivå som skickas till butiken. Det är här du ställer in rottillståndet, och se till att behålla varje funktion i sin egen gren.

importera { combineReducers } från 'redux'; importera todosReducer från './features/todos/reducer'; const rootReducer = combineReducers({ todos: todosReducer }) export standard rootReducer

Använda Redux In React

Naturligtvis är inget av detta användbart om det inte är kopplat till React. För att göra det måste du linda in hela appen i en leverantörskomponent. Detta säkerställer att det nödvändiga tillståndet och krokarna överförs till varje komponent i din app.

Annons

I App.js eller index.js, var du än har din rotrenderingsfunktion, linda din app i en <Provider> och skicka den till butiken (importerad från /store/index.js) som en rekvisita:

import Reagera från 'react'; importera ReactDOM från 'react-dom'; // Redux Setup import { Provider } från 'react-redux'; importera butik, { historik } från './butik'; ReactDOM.render(     <Provider store={store}> <App/>     </Provider>     , document.getElementById('root'));

Du är nu fri att använda Redux i dina komponenter. Den enklaste metoden är med funktionskomponenter och krokar. För att till exempel skicka en åtgärd använder du useDispatch()-kroken, som låter dig anropa åtgärder direkt, t.ex. dispatch(todosActions.addTodo(text)).

Följande behållare har en ingång kopplad till det lokala React-tillståndet, som används för att lägga till en ny uppgift till tillståndet närhelst en knapp klickas:

import React, { useState } från 'react'; import './Home.css'; importera { TodoList } från 'components' import { todosActions } från 'store/features/todos' import { useDispatch } från 'react-redux'-funktionen Home() {   const dispatch = useDispatch(); const [text, setText] = useState(“”); function handleClick() {     dispatch(todosActions.addTodo(text)); setText(“”); }   function handleChange(e: React.ChangeEvent<HTMLInputElement>) {     setText(e.target.value); }   retur (     <div className=”App”>       <header className=”App-header”>         <input type=”text” value={text} onChange={handleChange} /> button on ={handleClick}>           Lägg till Ny att göra          </button>         <TodoList />       </header>     </div> ); } export standard Hem;

När du sedan vill använda data som lagras i tillstånd, använd useSelector-kroken. Detta tar en funktion som väljer en del av tillståndet för användning i appen. I det här fallet ställer den in inläggsvariabeln till den aktuella listan med uppgifter. Detta används sedan för att återge ett nytt att göra-objekt för varje post i state.todos.

import React från 'react'; import { useSelector } från 'butik' importera { Container, List, ListItem, Title } från './styles' funktion TodoList() {   const posts = useSelector(state => state.todos)   return ( ;& lt return ( ;& lter List>         {posts.map(({ id, title }) => (           <ListItem key={title}>              <Title>{title} : {id}<   : {id}<  )}       </List>     </Container>   ); } export standard TodoList;

Du kan faktiskt skapa anpassade väljarfunktioner för att hantera detta åt dig, sparade i mappen /features/ på samma sätt som åtgärder och reducerare.

När du har ställt in allt och räknat ut kanske du vill undersöka hur du konfigurerar Redux Devtools, ställer in mellanprogram som Redux Logger eller connected-react-router, eller installerar en bieffektmodell som Redux Sagas.