Цель — Минимальный код для понимания избыточного потока.

Посмотреть код в действии в jsbin

Приложение счетчика с двумя кнопками для увеличения и уменьшения количества и меткой для отображения текущего количества.

Компоненты:
Добавить — компонент для увеличения значения счетчика на 1
Вычесть — компонент для уменьшения значения счетчика на 1
Результат — метка, показывающая значение счетчика в любой данное время

// Redux react binding
const { connect, Provider } = ReactRedux;

/**
 Result is a simple component which shows value of property "count" which is passed to it "this.props.count"
 The example below is ES6 class but it can simply be a function like this:
 const Result = ({count})=>(<label>{count}</label>);

 **/

class Result extends React.Component{
    constructor(props){
        super(props);
    }

    render(){
        return <label>{this.props.count}</label>;
    }
}
/**
Now we need to connect this component to redux store. A single store for entire application acting as single source of truth. Using react-redux connect function we can provide a mapping function to store. 
**/
mapResultStateToProps = (state)=>{return {count:state.count}};

Result = connect(mapResultStateToProps)(Result);
/**
{count:state.count}
key "count" is a prop which the store will bind to state's count property. Since the store has reference to component as well as soon as the state count property is changed the store will update the "props" of </Result> as well.
 If you put a lifecyle hook for willComponentReceiveProps you will be able to see the prop coming

 **/
/**
 On similar lines following two are Add and Substract component. 
 Again ES6 class can be replaced with function expression as no state information is needed for these as well.
 **/
class Add extends React.Component{
    constructor(props){
        super(props)
    }

    render(){
        //on click call the add method being passed as a prop to this component
        return <button onClick={this.props.add}>Add</button>;
    }
}
// this this component doesnt need to listen to any of store/state
mapAddStateToProps = (state)=>({});
/**
 connect method accept another method to bind dispatch events to component property
 Here "add" is a prop which we are passing to the component (this.props.add) which dispatches the ADD event

 **/
mapAddDispatchToProps = (dispatch) =>{
    return {
        add: ()=>{
            dispatch({type:"ADD"})
        }
    }
}

Add = connect(mapAddStateToProps, mapAddDispatchToProps)(Add);


class Substract extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return <button onClick={this.props.substract}>Substract</button>;
    }
}
mapSubstractStateToProps = (state)=>({});

mapSubstractDispatchToProps = (dispatch) =>{
    return {
        substract: ()=>{
            dispatch({type:"SUBSTRACT"})
        }
    }
}

Substract = connect(mapSubstractStateToProps, mapSubstractDispatchToProps)(Substract);

/**
 A note on mapper functions and connect.
 mapping method outside the component makes the component unaware of redux and state and abstracts it.
 "connect" helps in wrapping the component to provide these mapping otherwise each component need to manually subscribe and unsubscribe the store and add lot of redux specific code like dispatch, state etc.
 **/


/**
 Finally our App component using all above components
 **/
const App = () => <div>
    <Result/>
    <Add />
    <Substract />
</div>



/**
 Till now we have subscribed the components to store and also send events.
 Now we need a place where all our state manipulation logic resides which listens to the
 dispatched actions and update the state.
 This is called reducer - A function which updates the state based on action and data received. state update should be immutable.

 **/

const rootReducer = (state={count:0}, action) =>{
    let count;
    switch(action.type){
        case "ADD":
            //should be immutable use Object.assign or spread operator
            console.log("ADD");
            count = state.count +1;
            return {...state, count};

        case "SUBSTRACT":
            console.log("SUBSTRACT");
            count = state.count == 0? state.count:state.count - 1;
            return {...state, count};

        default:
            return state;
    }
}

/**
 Finally, the Redux store. It needs
 - reducer which it will delegate the actions when received to provide an new state in return. 
 - components which want to subscribe the state update information (this we did using connect)

 **/
var store = Redux.createStore(rootReducer);


/**
 Now to pass the store to everyone by wrapping the topmost component inside react-redux Provider
 **/

ReactDOM.render(
    <Provider store={store}><App/></Provider>,
    document.getElementById('root')
)

Для средних и крупных приложений лучше иметь отдельный класс ActionCreator, в котором все действия хранятся или классифицируются вместе.

Кроме того, это пример приложения для демонстрации потока. В идеале у вас должен быть только компонент-контейнер, подключающийся к хранилищу.

По мере роста приложения функция единственного редуктора имеет тенденцию к росту. Чтобы управлять этим сокращением, предоставьте combReducers, которым вы можете предоставить несколько функций редюсера, и он возвращает функцию rootReducer.

Для асинхронного действия посмотрите Redux Thunk или Redux saga.

удачного взлома!

БОНУС: Делаем то же самое в mobx

Если вам понравилась эта история, нажмите кнопку 👏 и поделитесь ею, чтобы помочь другим найти ее!