Redux
Eine Übersicht für Redux (Weiterentwicklung von Flux).
Links:
- Redux auf GitHub
- Blog-Artikel: Five Tips for working with Redux in large applications
- Blog-Artikel: 10 Redux tips to scale your dev team
- Blog-Artikel Performance-Optimierung mit
areStatesEqual
TODO Anschauen redux-ducks - Code-Konvention, wie man Actions, Action creator und Reducer definiert.
-
Abwandlung von
redux-duck
:// actions.js export const actionUserAdd = 'user/add' export const userAdd = (userName) => { type: actionUserAdd, userName } // reducers.js import { * } from './actions' const user = (state = {}, action) => { switch (action.type) { case actionUserAdd: return { ... } default: return state } }
TODO Anschauen: redux-devtools-extension - Extension für Chrome und Firefox.
-
Einbindung als Redux-Middleware:
const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() )
TODO Action creators
-
Vorteile:
- Es gibt eine explizite Abhängigkeit zwischen einer Komponente, die eine Aktion auslöst und der Action.
- Es gibt eine explizite API mit den Attributen der Action.
-
Man kann Action creator direkt mit
connect
ausreact-redux
verwenden:// connect ohne Action creator const mapDispatchToProps = dispatch => { return { addUser: payload => dispatch({type:'my-app/users/ADD',payload}) } } const connect(null, mapDispatchToProps)(MyComponent) // connect mit Action creator import {addUser} from 'app/state/actions' const connect(null, {addUser})(MyComponent)
TODO Anschauen: bindActionCreators
TODO Anschauen: Lib redux-thunk
integriert AJAX calls in Redux
- Probleme von
redux-thunk
:- Es propagiert die dispatch-Funktion überall hin. Das macht es schwieriger er erkennen, wer eine Action dispatcht. Dadurch können normale Action creators evtl. nicht mehr frei von Seiteneffekten sein.
TODO Anschauen: redux-saga
-
Zitat aus "10 redux tipps...":
It provides high level abstraction and patterns, like debounces, retries or throttles. It leaves actions as plain objects easier to unit test, and isolate all the side-effects in nice looking sagas, leaving your Redux code pristine.
For instance, if you plan one day to have an offline mode, I highly recommend you to onboard from the start something more advanced like redux-saga. Refactoring this part later is a real pain.
TODO State normalisieren
Quelle: Blog-Artikel 10 Redux tips to scale your dev team
// Nicht normalisiert
const state = {
users: [
{ id: 1, name: "Bat", groups: [{ name: "admin" }, { name: "regular" }] },
{ id: 2, name: "Vince", groups: { name: "admin" } },
{ id: 1, name: "Romain", groups: { name: "normal" } }
]
}
// Normalisiert
const state = {
users: {
1: { id: 1, name: "Bat", groups: [1, 2] },
2: { id: 2, name: "Vince", groups: [1] },
3: { id: 3, name: "Romain", groups: [2] }
},
groups: {
1: { id: 1, name: "admin" },
2: { id: 2, name: "regular" }
}
}
Für einfacheren Zugriff aus normalisierten State Selektoren verwenden:
const getGroupsById = (state, ids) => ids.map(id => state.groups[id])
const getUsersWithGroupsById = (state, ids) => ids.map(id => ({...state.users[id], groups: getGroupsById(state.users[id].groups)}))
TODO Anschauen: redux-logger - Logging-Middleware für Redux
Was ist Redux?
TODO Formulieren
flux
Core concepts
One way data flow
Views send actions to dispatcher
Actions are only data, no logic
Dispatcher sends action to store
Store changes data
Views update
No event chaining
Hauptunterschied zu MVC. Der Hauptgrund, warum Facebook Flux als Ersatz für MVC entwickelt hat
Entire application state is resolved in store before views update
Data manipulation only happens in one place
Offizielle FB Flux Lib
Nicht benutzen! OK zum rumspielen, ist aber mehr eine Art Proof-of-Concept zum Konzept-Paper
Benötigt viel Boilerplate-Code
ca. 20 Alternativen mit ähnlichem Konzept
Redux
Beliebteste Flux-Implementierung
Leicht geändertes Konzept zur originalen Flux-Architektur
Siehe [Video](https://www.youtube.com/watch?v=p8tqhf5qKOI?t=14m37s)
Dispatcher ist Teil des Store
Es gibt nur einen Store (state tree)
Store verwendet Reducers
Reducer ändern die Daten (Im originalen Flux macht dies der Store)
Dadurch ist Daten-Änderungs-Logik gut gekapselt
Store wird sehr einfach und hält nur noch die Daten und macht Event-Handling
Views/Actions wie Original-Flux
Redux verwenden
src/app/state/actions.js
anlegen:
export const myAction = (param) => ({
type: 'myAction',
param
})
src/app/state/reducers.js
anlegen:
import { combineReducers } from 'redux'
/*
* App state:
*
* {
* navigation: {
* sidebar: 'enter-routing'
* }
* }
*/
const navigation = (state, action) => {
state = state || {
sidebar: 'my-sidebar'
}
switch (action.type) {
case 'myAction':
return {
// ...
}
default:
return state
}
}
export default combineReducers({
navigation
})
src/app/state/store.js
anlegen:
import { createStore } from 'redux'
import mainReducer from './reducers'
export default createStore(mainReducer)
Actions dispatchen:
import { myAction } from 'app/state/actions'
store.dispatch(myAction(myParamValue))
Auf Store-Änderungen hören (Low-level ohne state-Selection):
let unsubscribe = store.subscribe(() => {
// Called every time after an action
})
unsubscribe()
Auf Store-Änderungen hören (mit state-Selection):
let unsubscribe = observeStore(
store,
state => state.navigation, // Select state
navigation => {
// Called every time if `state.navigation` has changed
})
unsubscribe()
Dazugehörige Util-Methode observeStore
:
/**
* Observes a react store with state selection
*
* @param store the react store
* @param select {function} selects the wanted part of the state.
* Signature: (state) => substate
* @param onChange {function} Called when the selected sub state has changed.
* Signature: (substate) => void
* @returns {function} Unsubsriber - call it to unsubscribe.
* Signature: () => void
*/
// Source see: https://github.com/reactjs/redux/issues/303#issuecomment-125184409
export function observeStore(store, select, onChange) {
let currentState;
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
let unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
Redux mit React
Aus Tutorial Using Redux with React:
From the very beginning, we need to stress that Redux has no relation to React. You can write Redux apps with React, Angular, Ember, jQuery, or vanilla JavaScript.
That said, Redux works especially well with libraries like React and Deku because they let you describe UI as a function of state, and Redux emits state updates in response to actions.
Links:
- Video Hot Reloading with time travel - Erste Vorstellung von Redux. Sehr eindrucksvoll.
Mit Demo von:- Schnellem Arbeiten mit kurzem Turn-Around durch Zusammenspiel von React mit react-hot-loader und Webpacks HMR (Hot Module Replacement). Details siehe React.
- Time Travel durch Verwendung von Redux mit einem Monitor-Dev-Tool (redux-devtools-dock-monitor). Demo siehe Video.
- Video zum Konzept: Presentational and Container Components
- Tutorial: Using Redux with React
- Advanced tutorial - how to handle network requests and routing
Konzepte
Quelle: Tutorial Using Redux with React
Trennung von "Presentational Components" und "Container Components":
Presentational Component | Container Component | |
---|---|---|
Aufgabe | Aussehen (Markup, Styles) | Funktionsweise (Daten laden, State-Updates) |
Kennt Redux | nein | ja |
Daten lesen | Liest Daten aus Props | Hört auf Redux-State |
Daten ändern | Ruft Callback aus Props auf | Dispatcht Redux-Actions |
Werden geschrieben | von Hand | generiert von react-redux (durch connect ) |
Presentational Components sind also ganze normale React-Komponenten ohne Abhängigkeit zu Redux. Es sind die Container Components, die die Verbindung zu Redux herstellen.
Verwendung
Redux und react-redux lokal installieren:
npm install --save redux react-redux
Wie oben beschrieben src/state/actions.js
, src/state/reducers.js
und src/state/store.js
anlegen.
Damit die Container auf den Store zugreifen können, wrappt man die gesamte Applikation mit einem Provider
- dieser stellt den Store per React Context zur Verfügung:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from 'app/state/store'
import Main from 'app/ui/main/Main'
render(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById('root')
)
Beispiel für Container:
import { connect } from 'react-redux'
import MyView from 'components/MyView' // MyView is the presentational component to hook up
export default connect(
// Map state to props
(state) => {
return {
myProp: state.someValue
}
},
// Map action creators to props (optional)
{
onBla: (params) => createMyAction(params),
onBlubb: createMyAction
}
)(MyView)
Mischform aus Presentational Component und Container ist auch möglich:
import React from 'react'
import { connect } from 'react-redux'
import MySidebar from 'component/MySidebar'
function render(props) {
var sidebarView = null;
if (props.sidebar == 'my-sidebar') {
sidebarView = (<MySidebar />)
}
return (
<div className="MyView">
{sidebarView}
</div>
)
}
export default connect(
// Map state to props
(state) => {
return {
sidebar: state.navigation.sidebar
}
},
// Map action creators to props (optional)
{
onBla: (params) => createMyAction(params),
onBlubb: createMyAction
}
)(render)