React + Redux — kurs: wprowadzenie i podstawy

Ten wpis jest 25 częścią z 39 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami

Redux! Kurs Reacta dorobił się odcinka o Reduksie! Powoli wprowadzę Cię świat Reduksa, nauczysz się używać tej łatwej biblioteki, poznasz koncepty stojące za nią i napiszesz prostą aplikację.

Redux: Oto wpis, na który wszyscy czekali! 🙂 Nie będzie teorii. Od razu zaczniemy używać Reduksa. Bo on jest tak naprawdę niesamowicie łatwy w obsłudze. Brzmi dobrze? Zaczynajmy!

Układ folderów przedstawiony w tym wpisie jest maksymalnie prosty. O strukturze większej aplikacji będę pisał jeszcze później.

Teoria

Jeśli jednak interesuje Cię teoria stojąca za Flux i Redux znajdziesz ją w innym moim wpisie:

Flux i Redux

Na pewno warto przeczytać o wzorcach projektowych stojących za Reduksem! Warto też zajrzeć do dokumentacji Redux.

Redux w praktyce

Do aplikacji z listą kontaktów (źródło tutaj: github.com/mmiszy/typeofweb-kurs-react/tree/contacts-list-1) dodasz Reduksa. Kontakty będą zapisywane w storze. Jeśli chcesz sobie odświeżyć wątek to zajrzyj do wpisu na temat łączenia Reacta z API.

Zacznij od zainstalowania paczek reduxreact-redux:

npm install --save react-redux redux

Praca z Reduksem składa się z 3 kroków:

  1. Stworzenie store
  2. Zdefiniowanie akcji
  3. Napisanie reducerów

To co mnie bardzo przytłaczało na początku pracy z Reduksem to ilość kodu, który musiałem napisać, a który „nic nie robił”. Nie martw się jednak! To tylko pozory. Im bardziej rozbudowana stanie się Twoja aplikacja tym tego kodu jest mniej, a zalety posiadania spójnego stanu są nieocenione.

Store w React

Store przechowuje stan całej Twojej aplikacji. Stwórz plik store.js, a w nim tylko kilka linii kodu:

import { createStore } from 'redux';
import reducers from './reducers';

export const store = createStore(reducers);

Następnie musisz sprawić, aby React mógł z tego store’a korzystać. Podstawowym sposobem na to jest zmodyfikowanie Twojego kodu w index.js i użycie komponentu <Provider>. Do tego komponentu musisz przekazać props store:

import { store } from "./store";
import { Provider } from "react-redux";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

Akcje w Redux

Akcje reprezentują zmiany stanu aplikacji. Użycie akcji to jedyny sposób na zmianę czegokolwiek w storze. Potrzebna Ci będzie (na razie) tylko jedna akcja reprezentująca nadejście danych z API. Nazwijmy ją FETCH_CONTACTS_SUCCESS.

Stwórz folder actions a w nim plik index.js. Umieścisz w nim funkcje zwracające akcje dla zadanych argumentów (action creators). Na razie jest tylko jeden action creator:

export const contactsFetched = (contacts) => ({
  type: 'FETCH_CONTACTS_SUCCESS',
  contacts
});

Reducery w Redux

Reducer to funkcja, która przyjmuje stan aplikacji oraz akcję i na tej podstawie generuje nowy, zaktualizowany stan. Nasz reducer nazwiemy contacts. A więc w folderze reducers stwórz plik contacts.js:

export const contacts = (state = [], action) => { // (1)
  switch (action.type) { // (2)
    case 'FETCH_CONTACTS_SUCCESS':
      return [
        ...action.contacts
      ]
    default:
      return state
  }
}

A CO TU SIĘ WYDARZYŁO? Spokojnie, powoli 😉
Reducer to nic innego jak funkcja, która przyjmuje obecny stan i akcję (1). Następnie sprawdza jaka to akcja (2). Jeśli jest to FETCH_CONTACTS_SUCCESS to oznacza, że mamy nową listę kontaktów i możemy cały obecny stan nią właśnie zastąpić. W każdym innym przypadku — zwracamy stan bez zmian.

combineReducers

Jak teraz powiedzieć reduksowi, aby użył Twojego reducera? Ponieważ reducerów może być wiele w aplikacji (a każdy z nich może operować na wycinku stanu), musisz je jakoś połączyć. Służy do tego funkcja combineReducers. Do folderu reducers dodaj plik index.js:

import { combineReducers } from "redux";
import { contacts } from "./contacts";

export default combineReducers({
  contacts
});

Połączenie Redux + React

Uff, to było skomplikowane. Szczęśliwie, to tylko troszkę boilerplate’u, a sama praca z Reduksem dalej jest już bardzo przyjemna!

Co teraz? Podłączamy aplikację do Reduksa! Jak? Używając funkcji connect.

Plan jest taki: „Udekorujesz” komponent App dzięki funkcji connect (jest to HoC, o którym będę pisał później). connect jako argumenty przyjmuje dwie funkcje zwyczajowo nazywane mapStateToPropsmapDispatchToProps

  • mapStateToProps — jako argument przyjmuje cały stan i musi zwrócić propsy dla danego komponentu
  • mapDispatchToProps — jako argument przyjmuje funkcję lub obiekt z action creatorami

connect

Kilka linii kodu wyraża więcej niż tysiąc słów:

import { connect } from "react-redux";
import { contactsFetched } from "./actions";

const mapStateToProps = (state) => {
  return {
    contacts: state.contacts // (1)
  }
};
const mapDispatchToProps = { contactsFetched }; // (2)

export const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App); // (3)

mapStateToProps zwracam interesujący mnie fragment stanu. Pamiętaj, że state w prawdziwej aplikacji może mieć kilka…dziesiąt własności. Mój komponent potrzebuje tylko contacts, więc tylko tyle mu przekazuję (1).

Dalej mapDispatchToProps definiuję jako obiekt z jednym action creatorem (2).

I ostatecznie eksportuję nowy komponent AppContainer, który powstaje w wyniku wywołania funkcji connect (3).

Pozostaje tylko użyć już nowopowstałego komponentu. W index.js zamiast <App /> używam <AppContainer />.

Nic się nie zmieniło

Zaiste. Nic się nie zmieniło, bo jeszcze nie użyłem nidzie action creatora (nadążasz?). Usuwam state z komponentu App i zamiast tego polegam tylko na action creatorach i storze:

class App extends React.Component {
  componentDidMount() {
    fetch("https://randomuser.me/api/?format=json&results=10")
      .then(res => res.json())
      .then(json => this.props.contactsFetched(json.results)); // (1)
  }

  render() {
    return (
      <div>
        <AppHeader />
        <main className="ui main text container">
          <ContactsList contacts={this.props.contacts} /> {/* (2) */}
        </main>
      </div>
    );
  }
}

Jak widzisz, zamiast setState używam this.props.contactsFetched (1). A do komponentu ContactsList przekazuję this.props.contacts (2).
Cały kod źródłowy tutaj: github.com/mmiszy/typeofweb-kurs-react/tree/contacts-list-2-redux

Przedstawiony tutaj sposób na pracę z asynchronicznymi funkcjami i Reduksem jest najprotszym z możliwych. W kolejnych wpisach przedstawię inne metody obsługi *asynchroniczności w Redux*.

Podsumowanie

Czujesz się podtopiona/y? Taki był zamiar. Napisaliśmy mnóstwo kodu, a efekt jest taki sam, jak wcześniej (a nawet gorszy!). Po co to wszystko?
Szczęśliwie ten boilerplate pisze się tylko raz. A rozbudowa tej aplikacji będzie teraz znacznie prostsza! Zobaczysz sam(a) w kolejnym wpisie 🙂 Poznaj React i Redux w dwa dni na szkoleniu!

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie: Dodaj do aplikacji napis „Ładowanie” w trakcie pobierania danych z API. Nie używaj .length 😉
Hint: Dodaj nową własność w storze, która będzie przechowywała informację o tym czy trwa pobieranie.

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami