Frontend

Captain Hook. Redux Store z wykorzystaniem React Hooks

Wraz z nadejściem ery Web 2.0 strony internetowe przestały być jedynie prostymi dokumentami tekstowymi, a zaczęły być aplikacjami raczej przypominającymi aplikacje desktopowe. Użytkownicy dostali możliwość tworzenia i dostosowywania treści do własnych potrzeb. Z konsumentów Internetu przeobrazili się w jego twórców. Wiązało się to wieloma wyzwaniami, które stanęły przed programistami w związku z nowymi, prężnie rozwijającymi się technologiami.

Chcąc dostarczyć użytkownikom jak najwięcej możliwości aplikacje klienckie zaczęły się rozrastać, a poziom ich skomplikowania spowodował, że także frontendowcy musieli zacząć myśleć o przechowywaniu i przepływie danych po stronie klientów. Co jednak zrobić, kiedy danych jest naprawdę dużo i stają się trudne do utrzymywania a ich przepływ niemożliwy do prześledzenia?

Tomasz Podkowski. Frontend Developer w firmie Merixstudio, gdzie chętnie podejmuje się projektów związanych z realizacją złożonych zadań wymagających kreatywnego podejścia do kodowania. Na co dzień podnosi swoje umiejętności programowania w Angularze, ale zgłębia również tajniki Reacta. Po godzinach relaksuje się przesłuchując swoją obszerną kolekcję płyt CD.


Z propozycją rozwiązania tego problemu wyszedł sam Facebook, prezentując architekturę Flux, która sama w sobie jest raczej wzorcem pisania kodu, niż pełnoprawnym frameworkiem. Dzięki zastosowaniu jednokierunkowego przepływu danych, pozwala na jego dokładne prześledzenie i przeanalizowanie, a co za tym idzie zdecydowanie ułatwia pracę z kodem i jego późniejsze utrzymanie.

Jej pełnoprawnym zastosowaniem, dostarczającym API, które zostało oficjalnie wsparte przez twórcę Fluxa jest Redux. Niewielki framework (~2kB włączając w to zależności). Choć najczęściej kojarzony z Reactem, brak jest jakichkolwiek przeciwwskazań do wykorzystania go w parze z jakąkolwiek inną biblioteką do tworzenia UI.

Redux

Redux przechowuje stan całej aplikacji w strukturze drzewa obiektów w tzw. store. Dzięki pojedynczemu storowi, naszemu Single Source of Truth całej aplikacji, jesteśmy w stanie łatwo debugować i podglądać cały przepływ danych. Wszelkie zmiany wykonywane są jedynie za pomocą pure functions gdyż w celu zablokowania mutacji stora przez side-effecty ten jest skonfigurowany tak, aby był “tylko do odczytu”.

Akcje, Typy i Payload

Akcja to zwykły obiekt, którego zadaniem jest opisanie co stało się w aplikacji. To przez dispatch akcji informujemy o zmianie stanu aplikacji. Każda akcja powinna być opisana przez typ – string opisujący do czego dana akcja służy.

dispatch({ type: 'INCREMENT' });
dispatch({ type: 'SET_COUNTER', counter: 1 });

Reducers

Żeby powiązać stan z akcjami tworzymy reducery. To nic innego jak funkcje reagujące na każdą dispatchowaną akcję. Przyjmują dwa argumenty: state oraz akcję. Nie modyfikują one bezpośrednio stanu, a jedynie zwracają jego zaktualizowaną kopię.

function counterReducer(state = [], action) {
  switch(action.type){
    case 'INCREMENT':
      return {
          ...state,
          counter: state.counter += 1,
        }
    case 'SET_COUNTER':
      return {
        ...state,
        counter: action.counter,  
	    }
		default:
      return state;
  }
}

Choć cała idea Reduxa wykorzystuje jedynie zwykły JavaScript, to jego integracja z daną biblioteką lub frameworkiem wymagać będzie wykorzystania konkretnego Redux API np. ng-redux czy react-redux. Czy możemy tego jakoś uniknąć? W Reactie tak.

Redux bez Reduxa?

React Hooks

Zaprezentowane w React 16.8. hooki w końcu pozwoliły użytkownikom tworzyć functional components bez obaw i problemów o zarządzaniem ich stanem. W końcu, aby móc korzystać ze state i lifecycle methods nie musimy już tworzyć class z ECMAScript 6. Hook to prosta funkcja, której parametrem jest wartość początkowa, a która zwraca listę dwóch elementów: pierwszym jest wartość, a drugim funkcja pozwalająca na zmianę tej wartości. Mała rzecz dająca spore możliwości.

const [counter, setCounter] = useState(initialCounter);

const increment = () => setCounter(counter + 1);

increment();

Redux + React Hooks?

Jak zatem wykorzystać hooki do stworzenia Redux API? Tym razem odwróćmy kolejność i zacznijmy od reducera.

function counterReducer(state = [], action) {
  switch (action.type) {
    case 'INCREMENT':
      return [...state, {
        counter: state.counter += 1,
      }];
		case 'SET_COUNTER':
      return [...state, {
        counter: action.counter,  
	    }];
    default:
      return state;
  }
}

Powyższy reducer jest niemalże identyczny, jak ten nieprzeznaczony do współpracy z hookami. Jedyną różnicą pomiędzy nimi jest format w jakim zwracają dane. W pierwszym przypadku jest to po prostu state, natomiast w tym lista z dwoma elementami. Stwórzmy coś na kształt Redux API.

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
	}

  return [state, dispatch];
}

Funkcja userReducer to nic innego jak custom hook, dzięki któremu uzyskujemy dostęp do statu oraz funkcji umożliwiającej jego zmianę. Oczywiście zanim to nastąpi dane kierowane są do reducera.

const [counter, dispatch] = useReducer(counterReducer, []);

function handleCounterSet(counter) {
  dispatch({ type: 'SET_COUNTER', counter });
}

handleCounterSet(1);

Twórcy Reacta przewidzieli, że zarządzanie data-flow naszej aplikacji przy wykorzystaniu reducerów jest na tyle częstym przypadkiem, że sami dostarczyli nam custom hook o nazwie useReducer.

const [state, dispatch] = useReducer(reducer, initialArg, init);

To w nim zawarli całą przedstawioną wcześniej logikę, dzięki czemu nie ma potrzeby pisania jej w całości od zera.

Zyski i straty

Dzięki wykorzystaniu React Hooks możemy stworzyć własną implementację Reduxa w naszej aplikacji. Nie musimy dzięki temu go pobierać i dodawać do projektu dodatkowych zależności, ale czy takie narzędzie będzie zawsze najlepszym wyborem? Odpowiem – pewnie nie. Redukujemy bundle o kilka kilobajtów, ale tracimy wszelką integrację choćby z takim narzędziem jak Redux DevTools. Wszystko więc jak zawsze zależy od projektu i wyobraźni programisty. Wybór technologii pozostawiam więc Wam.


Źródła:

baner

Zdjęcie główne artykułu pochodzi z unsplash.com.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://justjoin.it/blog/captain-hook-redux-store-z-wykorzystaniem-react-hooks" order_type="social" width="100%" count_of_comments="8" ]