Jak działają reduktory Redux

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Jeśli korzystałeś z Redux w dowolnym momencie podczas tworzenia aplikacji do zarządzania state , z pewnością natkniesz się na reduktory. Ten samouczek wyjaśni koncepcję reduktorów i sposób ich działania w Redux.

W tym samouczku nauczymy się koncepcji reduktorów i ich działania, szczególnie w aplikacjach React. Aby zrozumieć i lepiej korzystać z Redux, niezbędne jest solidne zrozumienie reduktorów. Reduktory umożliwiają aktualizację stanu aplikacji za pomocą akcji. Jest integralną częścią biblioteki Redux.

Ten samouczek jest przeznaczony dla programistów, którzy chcą dowiedzieć się więcej o Redux Reducers. Korzystne byłoby zrozumienie React i Redux. Pod koniec samouczka powinieneś lepiej zrozumieć rolę, jaką odgrywają w Reduxie Reduktory. Będziemy pisać dema kodu i aplikację, aby lepiej zrozumieć reduktory i ich wpływ na stan w aplikacji.

Co to jest reduktor

Reduktor to czysta funkcja, która przyjmuje stan aplikacji i akcji jako argumenty i zwraca nowy stan. Na przykład reduktor uwierzytelniania może przyjąć stan początkowy aplikacji w postaci pustego obiektu i akcji, która mówi mu, że użytkownik zalogował się i zwrócił nowy stan aplikacji z zalogowanym użytkownikiem.

Czyste funkcje to funkcje, które nie mają żadnych skutków ubocznych i zwrócą te same wyniki, jeśli zostaną przekazane te same argumenty.

Poniżej znajduje się przykład czystej funkcji:

 const add = (x, y) => x + y; add(2, 5);

Powyższy przykład zwraca wartość opartą na danych wejściowych, jeśli przekażesz 2 i 5 , zawsze otrzymasz 7 , o ile są to te same dane wejściowe, nic więcej nie wpływa na otrzymane dane wyjściowe, jest to przykład czystej funkcji.

Poniżej znajduje się przykład funkcji redukującej, która przyjmuje stan i akcję.

 const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }

Zdefiniujmy dwa parametry, które przyjmuje reduktor, state i action .

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Stan

Stan to dane, z którymi pracuje twój komponent — przechowuje dane wymagane przez komponent i dyktuje, co komponent renderuje. Po zmianie obiektu state komponent jest ponownie renderowany. Jeśli stan aplikacji jest zarządzany przez Redux, to w reduktorze zachodzą zmiany stanu.

Akcja

Akcja to obiekt, który zawiera ładunek informacji. Są jedynym źródłem informacji do aktualizacji sklepu Redux. Reduktory aktualizują sklep na podstawie wartości action.type . Tutaj zdefiniujemy action.type jako ADD_TO_CART .

Zgodnie z oficjalną dokumentacją Redux, działania to jedyne rzeczy, które wywołują zmiany w aplikacji Redux, zawierają ładunek dla zmian w sklepie aplikacji. Akcje to obiekty JavaScript, które informują Redux o typie akcji, która ma zostać wykonana, zwykle są one zdefiniowane jako funkcje takie jak ta poniżej:

 const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }

Powyższy kod to typowa wartość payload , która zawiera to, co wysyła użytkownik i będzie używana do aktualizacji stanu aplikacji. Jak widać z powyższego, obiekt akcji zawiera typ akcji oraz obiekt ładunku, który byłby niezbędny do wykonania tej konkretnej akcji.

Aktualizowanie stanu za pomocą reduktorów

Aby pokazać, jak działają reduktory, spójrzmy na poniższy licznik liczb:

 const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };

W powyższym kodzie, decreaseAction increaseAction akcje używane w reduktorze w celu określenia, do jakiego state jest aktualizowany. Następnie mamy funkcję redukującą o nazwie countReducer , która przyjmuje action i state początkowy o wartości 0 . Jeśli wartość action.type to INCREASE , zwracamy nowy stan, który jest zmniejszony o 1, w przeciwnym razie, jeśli jest to DECREASE , zwracany jest nowy stan zmniejszony o 1. W przypadkach, gdy żaden z tych warunków nie ma na myśli, zwracamy state .

Aktualizowanie stanu za pomocą reduktorów: operator rozprzestrzeniania się

Stan nie może być bezpośrednio zmieniony, aby utworzyć lub zaktualizować stan, możemy użyć operatora rozprzestrzeniania JavaScript, aby upewnić się, że nie zmieniamy wartości stanu bezpośrednio, ale zamiast tego zwracamy nowy obiekt, który zawiera przekazany do niego stan i ładunek użytkownika.

 const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }

W powyższym kodzie używamy operatora rozsunięcia, aby upewnić się, że nie zmieniamy wartości stanu bezpośrednio, w ten sposób możemy zwrócić nowy obiekt, który jest wypełniony przekazanym do niego stanem i ładunkiem, który jest wysyłany przez użytkownik. Używając operatora rozsunięcia, możemy upewnić się, że stan pozostanie taki sam, jak dodajemy do niego wszystkie nowe pozycje, a także zastąpić pole kontaktów w stanie, jeśli był wcześniej.

Redux Redux w akcji — wersja demonstracyjna

Aby lepiej zrozumieć Redux Reducers i sposób ich działania, zaimplementujemy prostą aplikację do wyszukiwania szczegółów filmu, której kod i działającą wersję można znaleźć tutaj na Codesandbox. Aby rozpocząć, przejdź do terminala i zainicjuj aplikację React za pomocą poniższego polecenia:

 create-react-app movie-detail-finder

Po zainicjowaniu naszego projektu, zainstalujmy pakiety, których potrzebujemy dla naszej aplikacji.

 npm i axios reactstrap react-redux redux redux-thunk

Po zainstalowaniu pakietów uruchommy nasz serwer deweloperski za pomocą polecenia:

 npm start

Powyższe polecenie powinno uruchomić nasz serwer rozwoju projektu w naszej przeglądarce. Następnie otwórzmy nasz projekt w wybranym przez nas edytorze tekstu, w naszym folderze src projektu, usuńmy następujące pliki: App.css , App.test.js , serviceWorker.js i setupTests.js . Następnie usuńmy cały kod, który odwołuje się do usuniętych plików w naszym App.js .

W tym projekcie będziemy używać Open Movie Database API, aby uzyskać informacje o naszych filmach, treści i obrazy dla naszej aplikacji, oto link do API, musisz się zarejestrować i uzyskać klucze dostępu, aby móc z niego korzystać aplikacja, Gdy skończysz, przejdźmy do naszej aplikacji, budując komponenty.

Budowanie komponentów aplikacji

Najpierw w naszym folderze src w katalogu naszego projektu utwórz folder o nazwie components i wewnątrz folderu utwórzmy dwa foldery o nazwie Movie i Searchbar , nasz komponent powinien wyglądać jak na poniższym obrazku:

folder komponentów
Folder komponentów. (duży podgląd)

Komponent filmu budowlanego

Zbudujmy komponent Movies , który określi strukturę szczegółów filmu, które otrzymamy z naszego API. Aby to zrobić, w folderze Movies naszego komponentu utwórz nowy plik Movie.js , następnie utwórz komponent oparty na klasie dla wyników API, zróbmy to poniżej.

 import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;

W powyższym kodzie, Używając komponentów z pakietu reactstrap , możesz zapoznać się z dokumentacją tutaj. Stworzyliśmy komponent Karta, który zawiera nazwę filmu, obraz, gatunek, aktora, rok, autora filmu, ocenę i fabułę. Aby ułatwić przekazywanie danych z tego komponentu, zbudowaliśmy dane tak, aby były rekwizytami dla innych komponentów. Następnie zbudujmy nasz komponent Searchbar .

Budowanie naszego komponentu paska wyszukiwania

Nasz komponent Searchbar będzie zawierał pasek wyszukiwania i komponent przycisku do wyszukiwania komponentów filmu, zróbmy to poniżej:

 import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }

W powyższym kodzie importujemy connect z react-redux , który służy do połączenia komponentu React ze sklepem Redux, dostarcza komponentowi informacje ze sklepu, a także udostępnia funkcje służące do wysyłania akcji do sklepu. Następnie zaimportowaliśmy komponent Movie i funkcję fetchMovie z akcji.

Następnie mamy tag formularza z polem wprowadzania tytułów naszych filmów, korzystając z haczyka setState z Reacta, dodaliśmy zdarzenie onChange i wartość, która ustawi stan title na wartość wprowadzoną w polu wprowadzania. Mamy znacznik button do wyszukiwania tytułów filmów i za pomocą zaimportowanego komponentu Movie przekazaliśmy właściwości komponentu jako props do wyniku wyszukiwania.

Następnie musimy napisać funkcję do przesłania tytułu naszego filmu do API w celu wysłania do nas wyników, musimy też ustawić stan początkowy aplikacji. zróbmy to poniżej.

 class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }

Tutaj ustawiliśmy początkowy stan aplikacji na puste ciągi, stworzyliśmy funkcję formHandler , która przyjmuje parametr zdarzenia i przekazuje funkcję fetchMovie z akcji oraz ustawia tytuł jako nowy stan aplikacji. Aby ukończyć naszą aplikację, wyeksportujmy ten komponent za pomocą właściwości connect z react-redux , w tym celu użyjemy właściwości mapToStateProps reakcji redux w celu wybrania części danych, których potrzebuje nasz komponent, więcej o mapToStateProps można dowiedzieć się tutaj.

 const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)

Dodajmy style do naszego formularza, tworząc plik Searchbar.module.css i dodając poniższe style:

 .Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }

Po wykonaniu powyższych czynności nasz komponent paska wyszukiwania powinien wyglądać podobnie do poniższego obrazu:

Komponent paska wyszukiwania
Komponent paska wyszukiwania. (duży podgląd)

Tworzenie akcji dla aplikacji

W tym komponencie będziemy konfigurować akcje Redux dla naszej aplikacji. Najpierw w katalogu src utwórz folder o nazwie actions a wewnątrz tego folderu utworzymy plik index.js . Tutaj stworzylibyśmy funkcję fetchMovie , która pobiera parametr title i pobiera film z API przy użyciu Axios. Zróbmy to poniżej:

 import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }

W powyższym kodzie zaimportowaliśmy axios i stworzyliśmy funkcję o nazwie fetchMovie , która przyjmuje parametr title za pomocą async/await, dzięki czemu możemy wysłać żądanie do serwera API. Mamy funkcję dispatch , która wysyła do Redux obiekt akcji, który jest do niego przekazywany. Z tego, co mamy powyżej, wysyłamy akcję typu FETCH_MOVIE i ładunku, który zawiera odpowiedź otrzymaną z API.

UWAGA: apikey w żądaniu zostanie zastąpiony Twoim własnym apikey po zarejestrowaniu się w OmdbAPI .

Tworzenie reduktorów aplikacji

W tej sekcji stworzymy redukcje do naszej aplikacji.

 const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;

W powyższym kodzie utworzyliśmy fetchMovieReducer , który przyjmuje domyślny stan null i parametr action , używając operatora przełącznika, w przypadku FETCH_MOVIE zwrócimy wartość action.payload , która jest filmem otrzymanym z API. Jeśli akcja, którą próbowaliśmy wykonać, nie znajduje się w reduktorze, zwracamy nasz stan domyślny.

Następnie utworzyliśmy funkcję rootReducer , która akceptuje bieżący stan i akcję jako dane wejściowe i zwraca fetchMovieReducer .

Składanie tego razem

W tej sekcji zakończymy naszą aplikację, tworząc nasz sklep redux w index.js , zróbmy to poniżej:

 import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )

W powyższym kodzie stworzyliśmy store z aplikacjami za pomocą metody createStore , przekazując stworzony przez nas reduktor oraz oprogramowanie pośredniczące. Middleware to dodatki, które pozwalają nam ulepszyć funkcjonalności Redux. Tutaj korzystamy z oprogramowania pośredniczącego Redux Thunk za pomocą applyMiddleware . Oprogramowanie pośredniczące Redux Thunk jest niezbędne, aby nasz sklep przeprowadzał asynchroniczne aktualizacje. Jest to potrzebne, ponieważ domyślnie Redux aktualizuje sklep synchronicznie.

Aby upewnić się, że nasza aplikacja zna dokładny sklep, którego ma używać, opakowaliśmy naszą aplikację w komponent Provider i przekazaliśmy sklep jako podporę, dzięki czemu inne komponenty naszej aplikacji mogą łączyć się i udostępniać informacje ze sklepem.

Dodajmy trochę stylu do naszego pliku index.css .

 *{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }

Renderowanie i testowanie wyszukiwarki szczegółów filmów

W tej sekcji zakończymy naszą aplikację, renderując naszą aplikację w naszym App.js , w tym celu stwórzmy komponent oparty na klasie o nazwie App i zainicjuj nasz pasek wyszukiwania i pole wejściowe.

 import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;

Tutaj utworzyliśmy komponent oparty na klasie App z h1 , który mówi Movie Search App i dodaliśmy nasz komponent Searchbar . Nasza aplikacja powinna wyglądać jak na poniższym obrazku:

aplikacja do szczegółów filmu z reduktorami
Finalna aplikacja szczegółów filmu za pomocą reduktorów. (duży podgląd)

Działające demo jest dostępne na Codesandbox.

Wniosek

Reduktory są ważną częścią zarządzania stanem Redux, dzięki reduktorom możemy pisać czyste funkcje, aby aktualizować określone obszary naszych aplikacji Redux bez skutków ubocznych. Poznaliśmy podstawy reduktorów Redux, ich zastosowania oraz podstawową koncepcję reduktorów, stanu i argumentów.

Możesz pójść dalej, przeglądając dokumentację dotyczącą reduktorów Redux tutaj. Możesz pójść dalej i budować więcej na reduktorach Redux, daj mi znać, co budujesz.

Zasoby

  • Dokumentacja React-Reux
  • Przeróbka dokumentacji
  • funkcja connect()
  • applyMiddleware funkcję oprogramowania pośredniczącego