Aan de slag met Redux voor JavaScript State Management

0
32

Redux is een hulpprogramma voor statusbeheer, speciaal gebouwd voor client-side JavaScript-toepassingen die sterk afhankelijk zijn van complexe gegevens en externe API's, en geweldige tools voor ontwikkelaars bieden waarmee u gemakkelijk met uw gegevens kunt werken.

Wat doet Redux?< /h2>

Simpel gezegd, Redux is een gecentraliseerde datastore. Al uw applicatiegegevens worden opgeslagen in één groot object. De Redux Devtools maken dit gemakkelijk te visualiseren:

Deze toestand is onveranderlijk, wat in het begin een vreemd concept is, maar om een ​​paar redenen logisch is. Als je de status wilt wijzigen, moet je een actie verzenden, die in feite een paar argumenten nodig heeft, een payload vormt en deze naar Redux stuurt. Redux geeft de huidige status door aan een reductiefunctie, die de bestaande status wijzigt en een nieuwe status retourneert die de huidige vervangt en een herladen van de betrokken componenten activeert. U kunt bijvoorbeeld een verloopstuk hebben om een ​​nieuw item aan een lijst toe te voegen, of een al bestaand item te verwijderen of te bewerken.

Als u het op deze manier doet, betekent dit dat u nooit ongedefinieerd gedrag krijgt met uw app-modificerende status naar believen. Omdat er een record is van elke actie en wat deze heeft veranderd, maakt het ook tijdreizen-foutopsporing mogelijk, waar je je applicatiestatus terug kunt scrollen om te debuggen wat er met elke actie gebeurt (net als een git-geschiedenis).

Redux kan met elk frontend-framework worden gebruikt, maar het wordt vaak gebruikt met React, en dat is waar we ons hier op zullen concentreren. Onder de motorkap gebruikt Redux de Context-API van React, die op dezelfde manier werkt als Redux en goed is voor eenvoudige apps als je helemaal van Redux wilt afzien. De Devtools van Redux zijn echter fantastisch bij het werken met complexe gegevens, en het is eigenlijk meer geoptimaliseerd om onnodige rerenders te voorkomen.

Advertentie

Als je TypeScript gebruikt, dingen zijn een stuk ingewikkelder om Redux strikt getypt te krijgen. U wilt in plaats daarvan deze handleiding volgen, die typesafe-actions gebruikt om de acties en reducers op een typevriendelijke manier af te handelen.

Structureren Uw project

Eerst wil je je mappenstructuur opmaken. Dit is aan jou en de stijlvoorkeuren van je team, maar er zijn in principe twee hoofdpatronen die de meeste Redux-projecten gebruiken. De eerste is het eenvoudigweg splitsen van elk type bestand (action, reducer, middleware, side-effect) in zijn eigen map, zoals:

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

Dit is echter niet de beste, omdat je vaak zowel een actie- als een reductiebestand nodig hebt voor elke functie die je toevoegt. Het is beter om de acties en reducers-mappen samen te voegen en ze op te splitsen per functie. Op deze manier zitten elke actie en het bijbehorende verloop in hetzelfde bestand. Je

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

Dit ruimt de import op, aangezien je nu zowel de acties als de reducers in dezelfde instructie met:

import { todosActions, todosReducer } from 'store/features/todos'

Het is aan jou of je Redux-code in zijn eigen map wilt houden (/store in de bovenstaande voorbeelden), of deze wilt integreren in de root src-map van je app. Als je al code per component scheidt en veel aangepaste acties en reducers schrijft voor elke component, wil je misschien de mappen /features/en /components/samenvoegen en JSX-componenten naast reducercode opslaan.< /p>

Als je Redux gebruikt met TypeScript, kun je een extra bestand toevoegen aan elke functiemap om je typen te definiëren.

Installeren en configureren van Redux

Installeer Redux en React-Redux vanaf NPM:

npm install redux react-redux

Je zult waarschijnlijk ook redux-devtools willen:

npm install –save-dev redux-devtools

Het eerste dat je wilt maken, is je winkel. Sla dit op als /store/index.js

import { createStore } from 'redux' import rootReducer from './root-reducer' const store = createStore(rootReducer) export default store; Advertentie

Natuurlijk wordt je winkel ingewikkelder als je dingen toevoegt zoals add-ons voor neveneffecten, middleware en andere hulpprogramma's zoals connected-react-router, maar dit is alles wat nu nodig is. Dit bestand neemt de root-reducer en roept createStore() aan met behulp daarvan, dat wordt geëxporteerd voor gebruik door de app.

Vervolgens zullen we een eenvoudige takenlijstfunctie maken. U zult waarschijnlijk willen beginnen met het definiëren van de acties die deze functie vereist, en de argumenten die eraan worden doorgegeven. Maak een map /features/todos/ en sla het volgende op als types.js:

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

Dit definieert enkele tekenreeksconstanten voor de actienamen. Ongeacht de gegevens die u doorgeeft, heeft elke actie een type-eigenschap, een unieke tekenreeks die de actie identificeert.

U bent niet verplicht om een ​​typebestand als dit te hebben, omdat u gewoon de tekenreeksnaam van de actie kunt typen, maar het is beter voor de interoperabiliteit om het op deze manier te doen. U kunt bijvoorbeeld todos.ADD en herinneringen.ADD in dezelfde app hebben, zodat u niet elke keer _TODO of _REMINDER hoeft te typen wanneer u naar een actie voor die functie verwijst.

Sla vervolgens het volgende op. as /store/features/todos/actions.js:

import * as types from './types.js' export const addTodo = text => ({ type: types.ADD, text }) export const deleteTodo = id => ({ type: types.DELETE, id }) export const editTodo = (id, tekst) => ({ type: types.EDIT, id, tekst })

Dit definieert een paar acties met behulp van de typen uit de stringconstanten, waarbij de argumenten en het maken van de payload voor elk worden uitgelegd. Deze hoeven niet volledig statisch te zijn, aangezien het functies zijn. Een voorbeeld dat u kunt gebruiken, is het instellen van een runtime-CUID voor bepaalde acties.

Advertentie

Het meest gecompliceerde deel van code, en waar u het grootste deel van uw bedrijfslogica zult implementeren, bevindt zich in de verloopstukken. Deze kunnen vele vormen aannemen, maar de meest gebruikte setup is met een switch-instructie die elk geval afhandelt op basis van het actietype. Sla dit op als reducer.js:

import * as types from './types.js' const initialState = [ { text: 'Hello World', id: 0 } ] export default function todos(state = initialState, action) { switch (action.type) { case types .ADD: return [ …state, { id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, tekst: action.text } ] case types.DELETE: return state.filter(todo => todo.id !== action.id ) case types.EDIT: return state.map(todo => todo.id === action.id ? { .. .todo, tekst: action.text } : todo ) default: return state } }

De status wordt doorgegeven als een argument en elk geval retourneert een gewijzigde versie van de status. In dit voorbeeld voegt ADD_TODO een nieuw item toe aan de staat (met elke keer een nieuwe ID), verwijdert DELETE_TODO alle items met de opgegeven ID en wijst EDIT_TODO de tekst voor het item toe aan de opgegeven ID en vervangt deze.

< p>De initiële status moet ook worden gedefinieerd en doorgegeven aan de reducer-functie als de standaardwaarde voor de statusvariabele. Dit definieert natuurlijk niet je hele Redux-statusstructuur, alleen de sectie state.todos.

Deze drie bestanden zijn meestal gescheiden in complexere apps, maar als je wilt, kun je ook ze allemaal in één bestand, zorg er gewoon voor dat u correct importeert en exporteert.

Nu die functie compleet is, laten we hem aansluiten op Redux (en op onze app). Importeer in /store/root-reducer.js de todosReducer (en elke andere feature-reducer uit de /features/-map) en geef deze door aan combineReducers(), waardoor een root-reducer op het hoogste niveau wordt gevormd die aan de store wordt doorgegeven. Dit is waar je de root-status instelt, waarbij je ervoor zorgt dat elke feature op zijn eigen branch blijft.

import { combineReducers } from 'redux'; importeer todosReducer van './features/todos/reducer'; const rootReducer = combineReducers({ todos: todosReducer }) export standaard rootReducer

Redux gebruiken in React

Dit is natuurlijk allemaal niet handig als het niet is verbonden met React. Om dit te doen, moet u uw hele app in een Provider-component inpakken. Dit zorgt ervoor dat de benodigde status en hooks worden doorgegeven aan elk onderdeel in je app.

Advertentie

In App.js of index.js, waar je ook je root-renderfunctie hebt, wikkel je app in in een <Provider>, en geef het door aan de winkel (geïmporteerd uit /store/index.js) als een prop:

importeer Reageren van 'reageren'; importeer ReactDOM van 'react-dom'; //Redux Setup import {Provider} van 'react-redux'; importeer winkel, {geschiedenis } uit './store'; ReactDOM.render(     <Provider store={store}> <App/>     </Provider>     , document.getElementById('root'));

Je bent nu vrij om Redux in je componenten te gebruiken. De eenvoudigste methode is met functiecomponenten en haken. Als u bijvoorbeeld een actie wilt verzenden, gebruikt u de useDispatch()-hook, waarmee u acties rechtstreeks kunt aanroepen, bijv. dispatch(todosActions.addTodo(text)).

De volgende container heeft een invoer die is verbonden met de lokale React-status, die wordt gebruikt om een ​​nieuwe taak toe te voegen aan de status wanneer er op een knop wordt geklikt:

importeer React, {useState} van 'react'; importeren './Home.css'; import { TodoList } from 'components' import { todosActions } from 'store/features/todos' import { useDispatch } from 'react-redux' function Home() {   const dispatch = useDispatch(); const [text, setText] = useState(“”); functie handleClick() {     dispatch(todosActions.addTodo(text)); setText(“”); }   function handleChange(e: React.ChangeEvent<HTMLInputElement>) {     setText(e.target.value); }   return (     <div className=”App”>       <header className=”App-header”>         <input type=”text” value={text} onChange={handleChange} /      ={handleClick}>           Toevoegen Nieuwe taak         </button>         <TodoList />       </header>     </div>  ); } export standaard Home;

Als u vervolgens gebruik wilt maken van de gegevens die zijn opgeslagen in de staat, gebruikt u de useSelector-haak. Hiervoor is een functie nodig die een deel van de status selecteert voor gebruik in de app. In dit geval wordt de postvariabele ingesteld op de huidige lijst met taken. Dit wordt vervolgens gebruikt om een ​​nieuw todo-item weer te geven voor elk item in state.todos.

import React from 'react'; import { useSelector } from 'store' import { Container, List, ListItem, Title } from './styles' function TodoList() {   const posts = useSelector(state => state.todos)   return ( ;  er < List>         {posts.map(({ id, title }) => (           <ListItem key={title}>             <Title>{title} : {id}& lt; )}       </List>     </Container>   ); } export standaard TodoList;

Je kunt aangepaste selectorfuncties maken om dit voor je af te handelen, opgeslagen in de map /features/ net zoals acties en reducers.

Zodra je alles hebt ingesteld en uitgevogeld, wil je misschien het volgende doen kijk naar het instellen van Redux Devtools, het instellen van middleware zoals Redux Logger of connected-react-router, of het installeren van een neveneffectmodel zoals Redux Sagas.