Do tej pory WorkTimetable był tylko mniej lub bardziej ładnym widokiem. Jednak samym widokiem wiele nie zrobię nie ważne jak dopracowany byłby. Więc żeby mój projekt stał się prawdziwą aplikacją dodałem do niego Redux’a.
Czym jest Redux?
Redux jest małą biblioteką służącą do zarządzania stanem aplikacji. Wyewoluowała z idei Flux’a, której głównym założeniem jest jednokierunkowy przepływ danych. Polega to na tym, że wszystkie dane przechodzą przez identyczny cykl życia dzięki czemu logika aplikacji jest przewidywalna i łatwa do zrozumienia. Reduxa można w skrócie opisać przy pomocy 3 zasad:
- Pojedyncze źródło prawdy – stan aplikacji jest przechowywany w pojedynczym obiekcie
- Stan aplikacji można tylko odczytać -jego zmiana może wystąpić tylko w wyniku wywołania akcji, nie możemy bezpośrednio zmienić stanu
- Zmiany stanu odbywają się przy pomocy czystych funkcji
Główne elementy Redux’a
W Reduxie istnieją 3 główne elementy za pomocą których zarządzamy stanem aplikacji:
- Store – przechowuje stan naszej aplikacji. W całej aplikacji istnieje tylko jeden taki obiekt.
- Actions – są to akcje wywoływane w aplikacji np.: CREATE_EVENT. Tylko za ich pomocą możemy aktualizować nasz Store.
- Reducers – są to czyste funkcje, które obsługują akcje. Na podstawie poprzedniego stanu aplikacji oraz wywołanej akcji zwraca nowy stan aplikacji
Konfiguracja Reduxa w aplikacji
Tyle z teorii czas na praktykę. Konfigurację zacząłem od dodania odpowiednich zależności do projektu :
yarn add redux, react-redux, react-router-redux
Jednak samo pobranie bibliotek nie sprawi że wszystko zacznie działać więc trzeba dodać trochę kodu. Zacząłem od pliku index.js w którym dodałem następujący kod:
let store = createStore( reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); const history = syncHistoryWithStore(browserHistory, store) ReactDOM.render( <Provider store={store}> <Router history={history} routes={routes}></Router> </Provider>, document.getElementById('root') );
W funkcji createStore warta zauważenia jest linijka która jest odpowiedzialna za poprawne działanie Redux DevTool w przeglądarce. Jest to potężne narzędzie, które bardzo pomaga podczas tworzenia aplikacji i poświęcę niemu osobny wpis żeby go dogłębnie poznać i wyczerpująco opisać.
Oprócz dodania Redux DevTool w tym pliku ustawiam synchronizację store’a aplikacji z historią przeglądania i przekazuję store do aplikacji przy pomocy Provider’a. W funkcji createStore zmienna reducers jest stałą, która przechowuje wszystkie dostępne w aplikacji Reducery i jest zdefiniowana w osobnym pliku w następujący sposób:
const reducers = combineReducers({ calendar, routing: routerReducer });
Dzięki combineReducers możemy w aplikacji tworzyć wiele różnych reducerów i wewnątrz tej funkcji połączyć w jedną zmienną. Pomaga to w lepszym rozłożeniu struktury plików. Ostatnią rzeczą jaka została do zdefiniowania jest użyty przez nas calendar reducer, który wygląda następująco:
const calendar = (state=[], action)=>{ switch(action){ default: return state; } }
Jest to prosta funkcja, która przyjmuje aktualny stan oraz wywołaną akcję i zwraca nowy stan. Opcja default jest po to żeby w przypadku wystąpienia nieznanej akcji zwrócić aktualny stan aplikacji.
Jak połączyć Reduxa i komponenty z React’a?
Można powiedzieć, że tyle wystarczy żeby poprawnie skonfigurować Redux’a w aplikacji ale to nie znaczy, że moje komponenty będą mogły korzystać z jego możliwości.
Połączenie wszystkiego w całość jest proste co zaraz pokażę. Pierwsze co zrobiłem to stworzyłem nowy plik o nazwie calendar.container. Jego zadaniem jest połączenie reactowego komponentu z reduxem. Plik aktualnie wygląda następująco:
const mapStateToProps = (state) => { return { calendar: state.calendar } } const mapDispatchToProps = (dispatch) => { return {} } const CalendarContainer = connect( mapStateToProps, mapDispatchToProps )(Calendar)
mapStateToProps – tutaj definiujemy jakie obiekty ze stora chcemy by były dostępne w komponencie
mapDispatchToProps – tutaj łączymy funkcje jakie będą wywoływane w naszym komponencie z akcjami reduxa np.: kliknięcie na przycisk save powinno wywołać akcję ADD_ELEMENT
Na samym końcu łączymy obie powyższe rzeczy z naszym kontenerem. Ostatnią rzeczą, jaką musimy zrobić, to na nowo zdefiniować w pliku app.routes.js co będzie naszym komponentem. Teraz nie będzie to nasz zwykły komponent tylko kontener
indexRoute: { component: CalendarContainer }
Dość sporo roboty że złożyć wszystko w jedną całość. Jednak dzięki chwili wysiłku mamy teraz łatwo rozszerzalną strukturę, którą dzięki jednokierunkowemu przepływowi danych możemy w prostu sposób debbugować. Zalety takiej struktury widać dopiero przy dużych projektach, gdzie mamy dużą ilość plików i zależności.
Uff, znowu mi wyszedł dłuższy wpis niż chciałem ale nie miałem serca przerywać go w połowie. Następnym krokiem będzie stworzenie odpowiednich akcji dla kalendarza tak by wszystko zaczęło już powoli działać. Tak więc do usłyszenia wkrótce.