System Architecture

The development of this project is based on React + Redux + React-Route framework, using WebPack for modular construction, front-end programming language is JavaScript ES6, using Babel for conversion.

| - project | - build / / package build directory | - SRC / / project development source code | - actions / / redux action | - components / / redux component | -- - Containers / / redux container | - images / / static image | -- - | mixins / / generic function library - reducers / / redux of store operations | - configureStore. Js / / redux store mapping | - index. The js / / page entry | - routes. The js / / routing configuration | - index. The HTML / / entry documents | -. Babelrc/configuration/Babel | -- - Main. Js / / the story of its package | - package. The json / / package information | - webpack. Config. Js / / webpack configuration file | - readme. MdCopy the code
"Dependencies" : {" Babel - polyfill ":" ^ 6.7.4 ", "base - 64" : "^ 0.1.0 from", the "immutable" : "^ 3.7.6", "isomorphic - fetch" : "^ 2.2.1", ""," ^ 2.13.0 ", "normalizr" : "^ 2.0.1", "react" : "^ 0.14.8", "the react - datetimepicker" : "^ 2.0.0," "the react - dom" : "^ 0.14.8", "the react - redux" : "^ 4.4.1", "the react - story - spinner" : "^ 0.4.0", "the react - the router" : "^ 2.0.1," "the react - the router - redux" : "^ 4.0.1", "story" : "^ 3.3.1", "the story - immutablejs" : "0.0.8", "story - logger" : "^ 2.6.1 redux -", "thunk" : "^ 2.0.1"}, "devDependencies" : {" Babel - core ":" ^ 6.7.5 ", "Babel - loader" : "^ 6.2.4," "Babel - preset - es2015" : "^ 6.6.0", "Babel - preset - react" : "^ 6.5.0", "Babel - preset - stage - 1" : "^ 6.5.0 CSS -", "loader" : "^ 0.23.1", "file - loader" : "^ 0.8.5", "img - loader" : "^ 1.2.2", "less" : "^ 2.6.1", "less - loader" : "^ 2.2.3" and "mocha", "^ 2.4.5", "style - loader" : "^ 0.13.1", "url - loader" : "^ 0.5.7", "webpack" : "^ 1.12.14"}Copy the code

Webpack configuration

It is also a practical experience of a Webpack, have to say, on React the best partner, is this goods! It’s really powerful. It works.

var webpack = require('webpack'); Var path = require('path'); Var nodeModulesPath = path.join(__dirname, '/node_modules'); // set node_modules directory module.exports = {// configure entry: {bundle: './ SRC /index', vendor: ['react', 'react-dom', 'redux']}, // Configure the output directory output: {path: path.join(__dirname, '/build'), publicPath: "/assets/", filename: 'bundle.js' }, module: { noParse: [ path.join(nodeModulesPath, '/react/dist/react.min'), path.join(nodeModulesPath, '/react-dom/dist/react-dom.min'), Path. join(nodeModulesPath, '/redux/dist/redux.min'),], loaders: [// less loaders {test: /\.less$/, loader: 'style! CSS! Less '}, // Babel loader {test: /\.js$/, exclude: /node_modules/, loader: 'Babel - loader'}, / / picture loader (pictures more than 8 k will automatically turn base64 format) {test: / \. (GIF | JPG | PNG) $/, loader: Url? Limit = 8192 & name = "images / [name] [hash] [ext]"}, / / loading icon font file {test: / \. (woff | SVG | eot | the vera.ttf) $/, loader: 'url? Limit = 500&name =fonts/[name].[hash].[ext]'}]}, // external dependencies (not bundled into bundle.js) externals: {'citys': 'Citys'}, // plugins: [/ / new webpack HotModuleReplacementPlugin (), / / version online open new webpack DefinePlugin ({/ / define production environment "process. The env" : { NODE_ENV: JSON.stringify("production") } }), //new webpack.optimize.UglifyJsPlugin({ compress: { warnings: False}}), open / / / / version online public parts will be out to vendor. The new webpack.optimize.Com monsChunkPlugin in js (' vendor ', 'vendor. Js), / / than the use frequency and distribution of id to find the shortest id assigned to the use of high frequency module new webpack. Optimize the OccurenceOrderPlugin (), New webpack.noErrorSplugin ()],};Copy the code

extension-Webpack performance optimization

To minimize the

To slim down your JS (and your CSS, if you use CSS-Loader) webPack supports a simple configuration item:

new webpack.optimize.UglifyJsPlugin()Copy the code

This is a simple and effective way to optimize your WebApp. Webpack also provides modules and Chunks ids to separate the two. With the following configuration items, WebPack is able to compare the frequency and distribution of ids to find the shortest IDS to assign to the most frequently used modules.

new webpack.optimize.OccurenceOrderPlugin()Copy the code

Import file has higher priority for file size (import file compression optimization rate is as good as possible)

duplicate removal

If you’re using a library with a cool dependency tree, it might have duplicate files. Webpack can find these files and deduplicate them. This ensures that duplicate code is not bundled into the bundle, instead the runtime requests a wrapped function. Doesn’t affect the semantics

new webpack.optimize.DedupePlugin()Copy the code

This feature may add some cost to the entry module

Optimizations for Chunks

When coding, you may have added many segmentation points to load on demand. But after you compile it, you realize that there are too many small modules that cause a lot of HTTP wastage. Fortunately Webpack can handle this, and you can do two things to combine some requests:

  • Limit the maximum chunk count with

new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})Copy the code
  • Limit the minimum chunk size with

new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})Copy the code

Webpack manages these asynchronously loaded modules through merges (merges occur more often where the current chunk is multiplexed). Files that are not imported when the portal page loads will not be merged into chunk.

A single page

Webpack is designed for single-page apps where you can break your app up into chunks, and those chunks are loaded by route. The entry module contains nothing more than routes and some libraries. This is fine for the user to navigate through, but to initialize the page load you need two network requests: one to request the route and one to load the current content.

If you use HTML5’s HistoryAPI to let the URL affect the current content page. Your server knows which content page will be requested by the client. To save requests, the server can put the requested content module in the response header: add it as a script tag, and the browser will load both requests in parallel.


Copy the code

You can extract chunk filename from build STas (stats-webpack-plugin)

More pages

When compiling a multi-page app, you want to share some code between pages. This is simple from WebPack’s point of view: you just compile with multiple entry files

webpack p1=./page1 p2=./page2 p3=./page3 [name].entry-chunk.jsCopy the code
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    }
}Copy the code

Multiple entry files can be generated from above

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.jsCopy the code

But a chunk could be added to share some of their code. If your chunks have some common modules, I recommend a cool plugin called CommonsChunkPlugin that identifies common modules and dumps them into a file. You need to add two script tags to your page to import import files and shared module files.

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    },
    plugins: [
        new CommonsChunkPlugin("commons.chunk.js")
    ]
}Copy the code

An entry file can be generated from above

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.jsCopy the code

And sharing files

commons.chunk.jsCopy the code

To load commons.chunk.js in the page first, you can load xx.entry.chunk.js in many Commons chunks by selecting different entry files. And you can stack these Commons chunks.

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3",
        ap1: "./admin/page1",
        ap2: "./admin/page2"
    },
    output: {
        filename: "[name].js"
    },
    plugins: [
        new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
        new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
    ]
};Copy the code

Output result:

page1.html: commons.js, p1.js
page2.html: commons.js, p2.js
page3.html: p3.js
admin-page1.html: commons.js, admin-commons.js, ap1.js
admin-page2.html: commons.js, admin-commons.js, ap2.jsCopy the code

In addition, you can pack multiple shared files into one shared file.

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        commons: "./entry-for-the-commons-chunk"
    },
    plugins: [
        new CommonsChunkPlugin("commons", "commons.js")
    ]
};Copy the code

The organization of less

As a front-end engineer from a back end background, writing simple CSS doesn’t have the pleasure of having configurable and structured code. Therefore, it is a good choice to introduce less, either for the management of the code later, or to improve the reuse ability of the code.

global.less

This is a library that can be called globally. I like to write the color scheme of the project, the font size, the methods used to import and export, etc. Other Container pages import it with @import and can use it. But there are two things to consider when defining it:

  • First, only variables and methods can be stored in less, and less will ignore them when compiled, only compiling CSS where they are called. So to prevent code duplication, instead of defining styles directly here, wrap them in a method that represents a purpose.

  • Second, the less method can only be initialized once if it defines styles for specific tags. It is recommended to do this in a single-page entry container for easy maintenance. For example, reset() (page label style initialization) is called globally only once in login.less of the entry container.

Here are some modules I use in my global.less

/** * @desc less * @createdate 2016-05-16 * @author Jafeney <[email protected]> **/ #ff634d; // Active background color (orange) @g-color-info: # 53b2EA; @g-color-primary: #459df5; // Main purpose of the background color (dark blue) @g-color-warning: #f7cec8; @g-color-success: #98cf07; // Success status background color (green) @g-color-fail: #c21f16; // Failed state background color (red) @g-color-danger: #ff634d; // Background color for warning (orange) @g-color-light: #fde2e1; // Global size @g-text-default: 14px; // Global size @g-text-default: 14px; @g-text-sm: 12px; @g-text-lg: 18px; @g-icon-logo: url("... ") @g-icon-logo: url("... /images/logo.png"); @g-icon-logoBlack: url(".. /images/logoBlack.png"); @g-icon-phone: url(".. /images/phone.png"); @g-icon-message: url(".. /images/message.png"); @g-icon-help: url(".. /images/help.png"); @g-icon-down: url(".. /images/down.png"); @g-icon-top: url(".. /images/top.png"); @g-icon-home: url(".. /images/home.png"); @g-icon-order: url(".. /images/order.png"); @g-icon-cart: url(".. /images/cart.png"); @g-icon-source: url(".. /images/source.png"); @g-icon-business: url(".. /images/business.png"); @g-icon-finance: url(".. /images/finance.png"); @g-icon-account: url(".. /images/account.png"); / /... // background color @g-color-grey1: #2a2f33; // black @g-color-grey2: #363b3f; // dark grey @g-color-grey3: #e5e5e5; // grey @g-color-grey4: #efefef; // grey @g-color-grey5: #f9f9f9; // very shallow @g-color-grey6: # FFFFFF; // white // global border @g-border-default: #e6eaed; @g-border-active: #53b2ea; @g-border-light: #f7dfde; Border-box () {box-sizing: border-box; -ms-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -webkit-box-sizing: border-box; } // simulate button effect.btn() {cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; &:hover { opacity: .8; } &.disabled { &:hover { opacity: 1; cursor: not-allowed; Text -overflow() {overflow: hidden; text-overflow: ellipsis; -o-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; -moz-text-overflow: ellipsis; white-space: nowrap; } // reset styles .reset() { // .... } // Some atoms class.atom () {.cp {cursor: pointer; } .ml-5 { margin-left: 5px; } .mr-5 { margin-right: 5px; } .ml-5p { margin-left: 5%; } .mr-5p { margin-right: 5%; } .mt-5 { margin-top: 5px; } .txt-center { text-align: center; } .txt-left { text-align: left; } .txt-right { text-align: right; } .fr { float: right; } .fl { float: left; }}Copy the code

componentThe less

In order to reduce the coupling of components, the less of each component must be written separately, with the style following the components, one less for each component, without other dependencies, to ensure the high portability of components. And components should have several styles for their purpose, such as button components, where we can provide different styles for color and provide them as a combination of styles for external use.

// The following variables can be configured for different needs @color-primary: #459df5; @color-warning: #f7cec8; @color-success: #98cf07; @color-fail: #c21f16; .btn { cursor: pointer; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; display: inline-block; box-sizing: border-box; -webkit-box-sizing: border-box; -ms-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; text-align: center; Opacity:.8; // Opacity:.8; Function: function: function: function: function: function: function: function: function: function: function: function: function: function: function: function: function: function cursor: not-allowed; } // full {color: # FFF; &.primary { background-color: @color-primary; border: 1px solid @color-primary; } / /... } // border {background-color: # FFF; &.primary { color: @color-primary; border: 1px solid @color-primary; } / /... }}Copy the code

containerThe less

As above, one less file per container. Try to wrap reusable modules into components instead of copying a few lines. This is convenient for a while, but as the project iterates, there will be more redundant code in the later period than you can imagine. If you follow the componentized design philosophy, you’ll find that the container contains only layout and sizing code, which is very easy to maintain.

This is the design essentials of large projects, in addition to the cultivation of the big picture, this is particularly important, as soon as the project is brought, do not start to write pages, but should spend more time in the design of the code, the global things out, the finer the better; Design reusable modules as components, think about the extensibility and different uses of components, remember – structure to minimize dependencies, maintain the independence of components, and use to consider the aggregation of functions, the so-called low coupling high aggregation.

However, in real projects, it is impossible for every component to exist independently. Sometimes, in order to further reduce the amount of code, we will combine some commonly used components into a large component to use, that is, composite components. So each project actually has a primary component (standalone) and a secondary component (composite). Tier 1 components can be migrated at will, while tier 2 components are created for real scenarios. There is no good or bad between the two.

The React organization

The React code of this project is written in ES6 style of JavaScript. The code is very elegant, and the language itself supports modularization. It is very cool to no longer rely on Browserify, RequireJS and other tools. If you don’t know ES6, please read Ruan Yifeng’s Introduction to ES6 Standards.

The entrance

The entry module index.js, located at the root of SRC, is the entry to external calls.

Import React from 'React' import {render} from 'react-dom' import {Provider} from 'react-redux' // Import router import {router, hashHistory } from 'react-router' import { syncHistoryWithStore } from 'react-router-redux' import routes from './routes' import configureStore from './configureStore' const store = configureStore(hashHistory) // Route store const History = syncHistoryWithStore(hashHistory, store) document.getElementById('root') )Copy the code

routing

The React-Route component is used to create hash routes. It is very simple to use, similar to the Navigator component in ReactNative.

import React from 'react' import { Route } from 'react-router' import Manager from './containers/manager' import Login from './containers/Login/' import Register from './containers/Register/' import Password from './containers/Password/' Import Dashboard from './containers/Dashboard/' const routes = (// main container // Dashboard/ /.... Container // Login // registration // Password retrieval for each module) export default routesCopy the code

Understand action, Store, reducer

The call relationship is as follows:

store.dispatch(action) --> reducer(state, action) --> final stateCopy the code

Here’s a practical example:

Action: current triggered action {type: 'xx'} // Return value: Var reducer = function(state, action){switch (action.type) {case 'add_todo': return state.concat(action.text); default: return state; }}; Reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer Var store = redux.createstore (reducer, []); // The state of the current store can be obtained by using store.getState() // The default value is the second argument passed to createStore console.log('state is: ' + store.getState()); // State is: // store. Dispatch (action) In redux, the only way to change state is through store.dispatch(action) store.dispatch({type: 'add_todo', text: 'read '}); // Print out the modified state console.log('state is: '+ store.getstate ()); // state is: read store.dispatch({type: 'add_todo', text: 'write '}); console.log('state is: ' + store.getState()); // State is reading and writingCopy the code

Mapping between Store, Reducer, and Action

Store: Flux refers to the data model and maintains a state variable internally. Use cases describe the state of the application. Store has two core methods, getState and Dispatch. The former is used to obtain the state of the store, while the latter is used to modify the state of the store.

Reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer reducer Var store = redux.createstore (reducer, []); // The state of the current store can be obtained by using store.getState() // The default value is the second argument passed to createStore console.log('state is: ' + store.getState()); // State is: // store. Dispatch (action) In redux, the only way to change state is through store.dispatch(action) store.dispatch({type: 'add_todo', text: 'read '});Copy the code

Action: An abstraction of behavior (such as user behavior), which is a normal JS object in Redux. Redux’s convention for actions is weak, except that the action must have a type field to identify the type of the action. So, the following are all legal actions

{type: 'add_todo' text: 'reading'} {type: 'add_todo' text: 'writing'} {type: 'add_todo' text: 'sleep, the time:' night '}Copy the code

Reducer: a normal function that modifies the store state. Pass in two arguments, state and action. Where, state is the current state (available via store.getState()), and action is the current triggered behavior (triggered by a call to store.dispatch(action)). The value returned by reducer(state, action) is the latest state value of the store.

Action: current triggered action {type: 'xx'} // Return value: Var reducer = function(state, action){switch (action.type) {case 'add_todo': return state.concat(action.text); default: return state; }}Copy the code

React programming thinking

Before I fled into React, I was a DOM operator. Whether it was jQuery or Zepto, DOM operation was what I used most in implementing page interaction. I bound complex interactions to documents step by step through selectors and event delegates, and then connected them one by one.

$(document).on('event', 'element', function(e){ e.preventDefault(); var that = this; var parent = $(this).parent(); var siblings = $(this).siblings(); var children = $(this).children(); / /... });Copy the code

React is very different from jquery-style programming thinking. React’s design is componentized. Each component maintains a uniform state through its lifecycle. When the state changes, the component updates and re-triggers render. This process actually operates on the virtual DOM in memory, not the actual DOM nodes, and because of its internal differential update algorithm, performance is better than traditional DOM operations.

Here’s a simple example:

Now to implement a modal component, if you are using jquery-style programming thinking, you are used to writing:

/** * @desc **/ var $= window.$; var modal = { confirm: Function (opts) {var title = opts. Title | | 'prompt', the content = opts. Content | | 'prompt content, the callback = opts. Callback. var newNode = [ '
       
', '
', '

', title, '

', '

', content, '

', '
', ' cancel', ' determine', '
', '
', '
', ].join(''); $('#J_mask').remove(); $('body').append(newNode); $('#J_cancel').on('click', function() { $('#J_mask').remove(); }); $('#J_confirm').on('click', function() { if (typeof callback === 'function') { callback(); } $('#J_mask').remove(); }); }}; module.exports = modal;Copy the code

It then triggers the modes and passes the parameters through a selector in the JavaScript of the page.

var Modal = require('modal'); var $ = window.$; var app = (function() { var init = function() { eventBind(); }; var eventBind = function() { $(document).on('click', '#btnShowModal', function() { Modal.confirm({ title: 'Hint ', content:' Hello! Callback: function() {console.log('Hello World'); }}); }); }; init(); }) ();Copy the code

Using React programming thinking, it would look something like this:

/** * @desc Component * @author Jafeney * @createdate 2016-05-17 ** / import React, { Component } from 'react' import './index.less' class Modal extends Component { constructor() { super() this.state = { jsMask: 'mask hidden' } } show() { this.setState({ jsMask: 'mask' }) } close() { this.setState({ jsMask: 'mask hidden' }) } confirm() { this.props.onConfirm && this.props.onConfirm() } render() { return (
       

{ this.props.title }

this.close()}>
{ this.props.children }
This. Confirm ()} > {this. Props. ConfirmText | | 'sure'} { this.props.showCancel && ( Cancel this. The close ()} >)}
); } } export default ModalCopy the code

It is then introduced as a label in the Render () function of the Container and triggered by a click.

import {React, component} from 'react'; 
import Modal from 'Modal';

class App extends Component {
    render() {
       
       
{this.refs.modal.show()}} this.modal = ref} onConfirm={()=>this.onModalConfirm()}>

Hello world!

} } export default AppCopy the code

You’ll notice that the code above does not deliberately manipulate the style of a DOM element, but instead triggers its own rendering function by changing the component’s state. In other words, instead of writing tedious DOM operations, we control component interactions and variations by changing component states. The benefits of this approach will be obvious once you get used to React. It can greatly reduce the amount of code required later.

To optimize the rendering

Render () is triggered by state changes in React, but Immutable is also used to render faster.

/** * @desc PureRender render **/ import React, {Component} from 'React' import Immutable from 'Immutable '; Export default {// deepCompare: (self, nextProps, nextState) => {return! Immutable.is(self.props, nextProps) || ! Immutable. Is (self.state, nextState)}, // Prevent unnecessary render loadDetection: (reducers=[])=> {for (let r of reducers) {if (! r.get('preload')) return (Copy the code
      
)}}}

This allows us to call it in the Render () function of the Container for rendering optimization

import React, { Component } from 'react' import PureRenderMixin from '.. /.. /mixins/PureRender'; Class App extends Component {render() {let {actions, account, accountLogs, bankBind} = this.props; / / data import detection error = PureRenderMixin. LoadDetection ([accountLogs account, BankBind]) if (error) return error return (
       
// something ...
); }}Copy the code

Global module processing

In fact, the biggest function of Redux is to effectively reduce the amount of code, abstract the tedious operations through action —-> Reducer —-> store, and finally maintain unified state. For global modules of a page, it is not enough to simply wrap mixins to call them, such as the global Request module. Here is how to modify it with Redux.

First declare it in types.js:

// request
export const REQUEST_PEDDING = 'REQUEST_PEDDING';
export const REQUEST_DONE = 'REQUEST_DONE';
export const REQUEST_ERROR = 'REQUEST_ERROR';
export const REQUEST_CLEAN = 'REQUEST_CLEAN';
export const REQUEST_SUCCESS = 'REQUEST_SUCCESS';Copy the code

Then write the action:

/** * @desc; // Fetch needs to use Promise's polyfill import {pendingTask, // The action key for modifying loading state begin, // The action value if a "long" running task begun end // The action value if a "long" running task ended } from 'react-redux-spinner'; import 'babel-polyfill' import fetch from 'isomorphic-fetch' import Immutable from 'immutable' import * as CONFIG from './config'; Import * as TYPES from './ TYPES '; export function request(route, params, dispatch, success=null, error=null, { method='GET', headers={}, Body =null} = {}) {dispatch({type: types.request_pedding, [pendingTask]: begin}) // Query const p = params? '? ' + Object.entries(params).map( (i)=> `${i[0]}=${encodeURI(i[1])}` ).join('&') : '' const uri = `${ CONFIG.API_URI }${ route }${ p }` let data = {method: method, headers: headers} if (method! ='GET') data.body = body fetch(uri, data) .then((response) => { dispatch({type: TYPES.REQUEST_DONE, [ pendingTask ]: end}) return response.json() }) .then((data) => { if (String(data.code) == '0') { if (method ! == 'GET' ) dispatch({type: TYPES.REQUEST_SUCCESS}); success && success(data); } else { console.log(data.error) dispatch({type: TYPES.REQUEST_ERROR, ... data}) error && error(data) } }) .catch((error) => { console.warn(error) }) } export function requestClean() { return { type: TYPES.REQUEST_CLEAN } }Copy the code

Then compile the corresponding reducer operation state:

import Immutable from 'immutable'; import * as TYPES from '.. /actions/types'; import { createReducer } from 'redux-immutablejs' export default createReducer(Immutable.fromJS({status: null, error: null}), { [TYPES.REQUEST_ERROR]: (state, action) => { return state.merge({ status: 'error', code: action.code, error: Immutable.fromJS(action.error), }) }, [TYPES.REQUEST_CLEAN]: (state, action) => { return state.merge({ status: null, error: null, }) }, [TYPES.REQUEST_SUCCESS]: (state, action) => { return state.merge({ status: 'success', error: null, }) } })Copy the code

Then expose the interface in the index.js of reducers

export request from './request'Copy the code

Why do I do this step? Because we need to further combine all reducer with combineReducers in configureStore.js:

import { createStore, combineReducers, compose, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import * as reducers from './reducers' import { routerReducer, routerMiddleware } from 'react-router-redux' import { pendingTasksReducer } from 'react-redux-spinner' export default function configureStore(history, initialState) { const reducer = combineReducers({ ... reducers, routing: routerReducer, pendingTasks: pendingTasksReducer, }) const store = createStore( reducer, initialState, compose( applyMiddleware( thunkMiddleware, routerMiddleware(history) ) ) ) return store }Copy the code

You can then use it in a Container, such as the login module:

Container * @createdate 2016-05-16 * @author Jafeney<[email protected]> **/ import React, { Component } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { replace } from 'react-router-redux' import { login } from '.. /.. /actions/user' import { requestClean } from '.. /.. /actions/request' import CheckUserMixin from '.. /.. /mixins/CheckUser' import PureRenderMixin from '.. /.. /mixins/PureRender' import '.. /style.less'; class Login extends Component { constructor() { super() } shouldComponentUpdate(nextProps, NextState) {// If (nextProps.user.getin (['login', 'status'])=='logged') { this.toMain() return true } return PureRenderMixin.deepCompare(this, nextProps, ComponentDidMount () {let {user} = this.props; If (CheckUserMixin. IsLogged (user) enclosing toMain ()} / / initialization page toMain () {this. Props. Actions. Replace ('/') This. Props. Actions. RequestClean ()} / / login to perform the login () {const userName = this. Refs [' J_username] value, password = this.refs['J_password'].value if (userName && password) { this.props.actions.login({username: userName, password: Enter password})}} / / binding onEnter event (event) {var e = event | | window. The event | | the arguments. The callee. The caller. The arguments [0]. If (e.keycode ==13) {// Enter this.login()}} render() {let {user} = this.props return (
       
this.onEnter()}>
Member login
This. The login ()} the className = "login - BTN" > log in Free registration | forgot password?
{ user.getIn(['login', 'error', 'message']) }
Function mapStateToProps(state) {return {user: state.user } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ login, requestClean, replace }, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(Login)Copy the code

Note: By doing this, the methods mounted in the component’s internal actions can be obtained via this.props.

reference

  • Webpack Performance Optimization

  • Redux series 01: Action, Store, Reducer from a simple Example

Welcome to my Github and personal blog -Jafeney