redux introduction
Reux is a predictable state manager for JavaScript applications.
Design patterns in redux
Decorator Model
Definition: Decorator pattern is used to dynamically add responsibilities to objects.
Let's look at the github code for the earliest redux (v0.2.0):
//Counter.js import React from 'react'; import { performs, observes } from 'redux'; @performs('increment', 'decrement','double') @observes('CounterStore') export default class Counter { render() { const { increment, decrement } = this.props; return ( <p> Clicked: {this.props.counter} times {' '} <button onClick={() => increment()}>+</button> {' '} <button onClick={() => decrement()}>-</button> {' '} <button onClick={() => double()}>double</button> </p> ); } }
After the observation is wrapped, the react component can access the couter data in the Redux store; after the performance is wrapped, the react component can initiate increment, decrement and double actions.
Let's see how performs package react components:
//performs.js import React, { Component, PropTypes } from 'react'; import pick from 'lodash/object/pick'; import identity from 'lodash/utility/identity'; const contextTypes = { getActions: PropTypes.func.isRequired }; export default function performs(...actionKeys) { let mapActions = identity; return function (DecoratedComponent) { const wrappedDisplayName = DecoratedComponent.name; return class extends Component { static displayName = `ReduxPerforms(${wrappedDisplayName})`; static contextTypes = contextTypes; constructor(props, context) { super(props, context); this.updateActions(props); } updateActions(props) { this.actions = mapActions( pick(this.context.getActions(), actionKeys), props ); } render() { return ( <DecoratedComponent {...this.actions} /> ); } }; }; }
Simple or not, performs is essentially a higher-order function that receives a parameter DecoratedComponent of the react component type and returns a higher-order component that wraps the passed react component and passes the action-related props to the component.
As can be seen from the figure above, Counter components are packaged by Observes and then by performs, forming a packaging chain.
In the API provided by redux, there is an important way to connect React components to Redux stores. The connection operation does not change the original component class, but returns a new component class that has been connected to the Redux store. What is the typical decorator pattern?
Observer model
Definition: The Observer pattern, also known as the Publish-Subscribe pattern, defines a one-to-many dependency between objects. When the state of an object changes, all objects that depend on it will be notified.
@observes('CounterStore')
This line of counter.js indicates that it subscribes to Redux's CountStore data. Let's look at the implementation of objserves:
//observes.js import React, { Component, PropTypes } from 'react'; import pick from 'lodash/object/pick'; const contextTypes = { observeStores: PropTypes.func.isRequired }; export default function connect(...storeKeys) { return function (DecoratedComponent) { const wrappedDisplayName = DecoratedComponent.name; return class extends Component { static displayName = `ReduxObserves(${wrappedDisplayName})`; static contextTypes = contextTypes; constructor(props, context) { super(props, context); this.handleChange = this.handleChange.bind(this); this.unobserve = this.context.observeStores(storeKeys , this.handleChange); //Subscribe to store data } handleChange(stateFromStores) { this.currentStateFromStores = pick(stateFromStores, storeKeys); this.updateState(stateFromStores); } updateState(stateFromStores, props) { stateFromStores = stateFromStores[storeKeys[0]]; const state = stateFromStores; this.setState(state);//Component updates through setState } componentWillUnmount() { this.unobserve();//Unsubscribe } render() { return ( <DecoratedComponent {...this.props} {...this.state} /> ); } }; }; }
When the data changes, the setState method is invoked to update the UI of the Counter component.