Skip to content

Instantly share code, notes, and snippets.

@luque
Last active July 27, 2016 23:57
Show Gist options
  • Save luque/ce8ad9f676e7ec72f33d448cee92514c to your computer and use it in GitHub Desktop.
Save luque/ce8ad9f676e7ec72f33d448cee92514c to your computer and use it in GitHub Desktop.
Notes about Redux
= Flux Architecture =
Flux is the application architecture that Facebook uses for building client-side web applications.
It complements React's composable view components by utilizing a unidirectional data flow.
It's more of a pattern rather than a formal framework..
== Overview ==
Flux applications have three major parts: the dispatcher, the stores, and the views (React components).
Additionally, action creators — dispatcher helper methods — are used to support a semantic API that describes a
ll changes that are possible in the application. It can be useful to think of them as a fourth part of the Flux
update cycle.
Flux eschews MVC in favor of a unidirectional data flow:
When a user interacts with a React view, the view propagates an action through a central dispatcher, to the various s
tores that hold the application's data and business logic, which updates all of the views that are affected.
Control is inverted with stores: the stores accept updates and reconcile them as appropriate, rather than depending
on something external to update its data in a consistent way.
Nothing outside the store has any insight into how it manages the data for its domain, helping to keep a clear
separation of concerns.
Stores have no direct setter methods like setAsRead(), but instead have only a single way of getting new data i
nto their self-contained world — the callback they register with the dispatcher.
== Data flow ==
Data in a Flux application flows in a single direction:
Action -> Dispatcher -> Store -> View
A unidirectional data flow is central to the Flux pattern, and the above diagram should be the primary
mental model for the Flux programmer.
The views may cause a new action to be propagated through the system in response to user interactions:
Action -> Dispatcher -> Store -> View
| |
+------- Action ------+
All data flows through the dispatcher as a central hub. Actions are provided to the dispatcher in an action
creator method, and most often originate from user interactions with the views. The dispatcher then invokes
the callbacks that the stores have registered with it, dispatching actions to all stores. Within their
registered callbacks, stores respond to whichever actions are relevant to the state they maintain. The
stores then emit a change event to alert the controller-views that a change to the data layer has occurred.
Controller-views listen for these events and retrieve data from the stores in an event handler. The
controller-views call their own setState() method, causing a re-rendering of themselves and all of their
descendants in the component tree.
We found that two-way data bindings led to cascading updates, where changing one object led to another
object changing, which could also trigger more updates. As applications grew, these cascading updates made
it very difficult to predict what would change as the result of one user interaction. When updates can only
change data within a single round, the system as a whole becomes more predictable.
== Stores ==
Stores contain the application state and logic. Their role is somewhat similar to a model in a
traditional MVC, but they manage the state of many objects — they do not represent a single record of
data like ORM models do.
Stores manage the application state for a particular domain within the application. (like an Aggregate in DDD jargon?)
== Views and Controller-Views ==
Stores manage the application state for a particular domain within the application.
Close to the top of the nested view hierarchy, a special kind of view listens for events that are
broadcast by the stores that it depends on. We call this a controller-view, as it provides the glue
code to get the data from the stores and to pass this data down the chain of its descendants. We might
have one of these controller-views governing any significant section of the page.
When it receives the event from the store, it first requests the new data it needs via the stores' public
getter methods. It then calls its own setState() or forceUpdate() methods, causing its render() method
and the render() method of all its descendants to run.
== Actions ==
The dispatcher exposes a method that allows us to trigger a dispatch to the stores, and to include
a payload of data, which we call an action.
This method may be invoked from within our views' event handlers, so we can call it in response to a user interaction.
Actions may also come from other places, such as the server. This happens, for example, during data
initialization. It may also happen when the server returns an error code or when the server has updates
to provide to the application.
== References ==
- http://facebook.github.io/flux/
- Hacker Way: Rethinking Web App Development at Facebook:
https://www.youtube.com/watch?v=nYkdrAPrdcw&index=26&list=PLb0IAmt7-GS188xDYE-u1ShQmFFGbrk0v
= Redux =
* 1st Principle: The single immutable state tree
Everything that changes in your application, including the data on the UI, is included in a single object we call
the State or the State tree.
The state is the minimal representation of the data in your app.
* 2nd Principle: The state tree is read only
Everytime you need to change the state you have to dispatch an action, a plain Javascript object describing that change.
The actions are the minimal representation of the change in your data.
The structure is up to you, but it need a type.
* Pure and impure functions
Pure functions -> return a value that depends only on the values of the arguments. They don't modify the values passed
to them. They don't have any *observable* side-effects.
Impure functions -> May have side-effects or modify the values they receive.
* 3rd principle: The Reducer function
Your UI is more predictable if can be described as a pure function of the application state.
Redux complements this approach with the idea that state mutations are described as a pure function that takes
the previous state and the action being dispatched as arguments and returns the next state of the whole application.
It doesn't modify the initial state, but returns a new state object.
This function is called the reducer.
* Writing a counter reducer with tests
The convention in Redux is that if reducer receives an undefined state of the application it should return
the initial state (using ES6 default arguments instead of conditions).
* Store methods: getState(), dispatch() and subscribe()
* Implementing store from scratch.
* React counter example.
* Avoiding array mutations with concat(), slice() and ...spread.
* Avoiding object mutations with Object.assign() and ...spread.
* Writing a TODO list reducer for Adding a TODO.
* Writing a TODO list reducer for Toggling a TODO.
* Reducer composition with Arrays.
* Reducer composition with Objects.
* Reducer composition with combineReducers().
* Implementing combineReducers() from scratch.
* React TODO list: Adding a TODO.
* React TODO list: Toggling a TODO.
* React TODO list: Filtering tools.
* Extracting presentational components: Todo, Todolist.
* Extracting presentational components: AddTodo, Footer, FilterLink.
* Extracting container components: FilterLink.
* Extracting container components: VisibleTodoList, AddTodo.
Container components provide the data and the behaviour for the presentational components. T
hey improve the reusability of the presentational components and avoid to pass a lot of props down from the
parent container trough the intermediate components.
* Passing the store down explicitly via props.-> Inconvenient because you have to pass the store down through
every container component, so they need to subscribe to the store, dispatch actions and access its state.
* Passing the store down implicitly via Context:
class Provider extends Component {
getChildContext() {
return {
store: this.props.store;
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object;
};
ReactDOM.render(
<Provider store={createStore(rootReducer)}>
<TodoApp />
</Provider>
);
Any child or grand-child component inside the Provider is going to receive the context with store.
Access using:
const { store } = this.context;
In functional components that doesn't have this the context is passed as the second argument after props:
const AddTodo = (props, { store }) => {...};
* Passing the store down with <Provider> from React Redux:
The Provider is so convenient that is included in a special library 'react-redux' with React bindings to
the Redux library:
import { Provider } from 'react-redux';
* Generating containers with connect() from React Redux:
Container components are very similar, so there is a facility to write them in an easier way:
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(
state.todos,
state.visibilityFilter
)
};
};
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch({
type: 'TOGGLE_TODO',
id
});
}
};
};
import { connect } from 'react-redux';
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps)(TodoList); ---> this is a curried function
Instead of create container components as classes you can define a variable and use the
connect() function from React-Redux specifying the maps to props and the presentational component
that you want to wrap and to pass the props to.
* Extracting action creators:
Action creators are functions to create the actions in a single place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment