applyMiddleware 与 Enhancer

import { createStore, applyMiddleware, compose } from 'redux'

const store = createStore(
  reducer,
  preloadedState, // <----- 可选,前后端同构的数据同步
  compose( // <------------ 还记得吗?compose 是从右到左的哦!
    applyMiddleware( // <-- 这货也是 Store Enhancer 哦!但这是关乎中间件的增强器,必须置于 compose 执行链的最后
      middleware1,
      middleware2,
      middleware3
    ),
    enhancer3,
    enhancer2,
    enhancer1
  )
)

Redux APIs

Compose(…funcs)

/**
 * compose(f,h,g)(...args) => f(g(h(...args)))
 * 使用了 reduceRight, 从右开始执行
 * @param {多个函数, 逗号隔开}
 * @return {函数}
 */

export default function compose(...funcs){
  if(funcs.length === 0){
    return arg => arg
  }

  if(funcs.length === 1){
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0,-1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
`</pre>

关键在于 reduceRight 可以传入初值

### createStore(reducer, [initialState])

<pre>`import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadState, enhancer){
  var currentReducer = reducer
  var currentState = preloadState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false
}

function ensureCanMutateNextListeners(){
  if(nextListeners === currentListeners){
    nextListeners = currentListeners.slice()
  }
}

function getState(){
  return currentState
}

function subscribe(listener){
  if(typeof listener !== 'function'){
    throw new Error('Expected listener to be a function')
  }

  var isSubscribed = true

  ensureCanmutateNextListener()
  nextListeners.push(listener)

  return function ubsubscribe(){
    if(!isSubscribe){
      return
    }
    isSubscribe = false

    ensureCanMutateNextListeners()
    var index = nextListeners.indexOf(listener)
    nextListeners.splice(index,1)
  }
}

function dispatch(action){
  if(isPlainObecjt(action)){
    throw new Error('Action must be plain objects ' + 'Use custom middleware for async action')
  }

  if(typeof action.type === 'undefined'){
    throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?')
  }

  if(isDispatching){
    throw new Error('Reducers may not dispatch actions.')
  }

  try{
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  var listeners = currentListeners = nextListeners

  for(var i = 0; i&lt; listeners.length, i++){
    listeners[i]()
  }

  return action
}

function replaceReducer(nextReducer){
  if(typeof nextReducer !== 'function'){
    throw new Error('Expected the nextReducer to be a function')
  }

  currentReducer = nextReducer
  dispatch({type:ActionTYpes.INIT})
}

### combineReudcers(reducers)

`</pre>

import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'

const rootReducer = combineReducers({
  counter: counterReducer,   // &lt;--- 键名是 state 中的属性, 键值是对应 reducer 函数名

  todos: todosReducer
})
export default rootReducer

<pre>`
如果提高需求
`</pre>

state
  ├── counter: 0
  ├── todo
        ├── optTime: []
        ├── todoList: [] # 这其实就是原来的 todos!

<pre>`
对应的 reducers 是:
`</pre>

reducers/
   ├── index.js &lt;-------------- combineReducers (生成 rootReducer)
   ├── counterReducer.js
   ├── todoReducers/ &lt;--------- combineReducers
           ├── index.js
           ├── optTimeReducer.js
           ├── todoListReducer.js

<pre>`
`</pre>

/*  reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducers from './todosReducer'

const rootReducer = combineReducers({
  counter: counterReducer,
  todos: todosReducers
})

export default rootReducer

=================================================

/* reducers/todosReducers/index.js */

import { combineReducers } from 'redux'
import optTimeReducer from './optTimeReducer'
import todoListReducer from './todoListReducer'

const todosReducers = combineReducers({
  optTime: optTimeReducer,
  todoList: todoListReducer
})

export default todosReducers

<pre>`
无论 dispatch 哪个 action 都会流通**所有** reducer, 但是由于 reducer 是纯函数, 效率还是会提高.

### BindActionCreator

### applyMiddlware(...middlewares)

先要理解 Middleware, enhancer

Redux 引入中间件机制是为了在 dispatch 前后进行处理

`</pre>

const printStateMiddleware = ({getState}) => next => action => {
  console.log('...')
  let returnValue = next(action)
  console.log('...')
  return returnValue
}

<pre>`
实际上最内层
`</pre>

function printStateMiddleware(middlewareAPI){ //中间件内可以使用的 API, 如 getState 与 dispatch
   return function(dispatch){ // 传入原 dispatch 的引用
    return function(action){
      console.log(...)
      var returnValue = dispatch(action)  //  执行 dispatch 行为, dispatch(action) 返回的还是 action
      console.log(...)
      return returnValue // 传给下一个中间件的 action
    }
   }
}

<pre>`
### Store Enhancer

说白了 Store Enhancer 是对生成 store 的 API 的改造, 与 middleware 的最大区别是 middlewere 不修改 store 的 API

改造 store 的 API 就要从 createStore 入口

Redux 的 applyMiddleware 就是一个 Enhancer

`</pre>

import compose from './compose' // 作用就是层层包裹

// 传入中间件
export default function applyMiddleware(...middlewares){
  // 传入 createStore
  return function(createStore){
    // 返回一个函数签名和 createStore 一样的函数, 即返回一个增强版的 createStore
    return function(reducer, preloadState, enhancer){
      // 用原 createStore 先生成一个 store, 包含 getState, dispatch, subscribe, replaceReducer 四个 API
      var store = creataStore(reducer, preloadState, enhancer)

<pre>`  var dispatch = store.dispatch //  生成指向原 dispatch的引用
  var chain = [] // 存储中间件的数组

  //提供给中间件的 API 其实就是 store 的 API
  var middlewareAPI = {
    getState: store.getStore,
    dispatch: (action) =&gt; dispatch(action)
  }

  // 给中间件添加 API
  chain = middlewares.map(middleware =&gt; middleware(middlewareAPI))

  // 串联中间件, 添加中间件的起点 store.dispatch, 返回经过修饰的 dispatch
  dispatch = compose(...middlewares)(store.dispatch)

  return{
    ...store, // store 中保留的原 API
     dispatch  // 用新的 dispatch 覆盖原 dispatch   
  }
}

}
}
`
参考

Middleware

You can include custom middleware functions to the dispatch method of your store.

The dispatch function is responsible for sending actions to one or more reducer functions for state changes. The composed specialized functions around the original dispatch method creates the new middleware capable dispatch method.

Source code for applyMiddleware(from Redux 1.0.1)

export default function applyMiddleware(...middlewares) {
  return (next) =&gt; 
    (reducer, initialState) =&gt; {
      var store = next(reducer, initialState);
      var dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) =&gt; dispatch(action)
      };

      chain = middlewares.map(middleware =&gt; middleware(middlewareAPI));

      return {
        ...store,
        dispatch
      }
    }
}
`</pre>

Composing Functions

Functional programming is very literal and very mathematical. In the case of composing functions with math you can express two or more functions like this:

<pre>`given:

f(x) = x^2 + 3x + 1
g(x) = 2x

then:
(f · g)(x) = f(g(x)) = f(2x) = 4x^2 + 6x + 1
`</pre>

It is no coincidence that you can compose two or more functions in a similar fashion. Here is a very simple example of a function that composes two functions to return a new specialized function:

<pre>`var greet = function(x) { return `Hello, ${x}`}
var emote = function(x) { return `${x} :)`}

var compose = function(f,g){
  return function(x){
    return f(g(x))
  }
}
`</pre>

This was just to illustrate the basic concept. We will look at a more generic way to solve that issue by examining the Redux Code.

### Currying

Another powerful functional programming concepts is the idea of currying or partially applying argument values to a function. By currying we can create a new specialized function that has partial information supplied to it. Here is the canonical example of currying where we have an add function that curries the first operand parameter to create a specialized add function

<pre>`var curriedAdd = function(a){
  return function(b){
    return a + b;
  }
}

var addTen = curriedAdd(10);
addTen(10); // 20
`</pre>

By currying and composing your functions you can create powerful new functions that create a pipeline for data processing.

### Redux Dispatch Function

A Store in Redux have a dispatch method which is only concerned with the main execution task you are interested in. You dispatch actions to your reducer functions to update state of the application. Redux reducers function takes a state and action parameter and return a new resultant state.

<pre>`reducer:: state -&gt; action -&gt; state
`</pre>

You might dispatch an action that simply sends a message to remove an item in a list which could look like this:

<pre>`{type: types.DELETE_ITEM, id: 10}
`</pre>

The store will dispatch this action object to all of it's reducer functions which could affect state. However the reducer functions are only concerned with executing logic around this deletion. They typically don't care who did it, how long it took, or logging the before and after effects of the state changes. This is where middleware can help us to address non-core concerns.

### Redux middleware

Redux middleware is designed by creating functions that can be composed together before the main dispatch method is invoked.

Let's creating a very simple logger middleware function that can echo the state of your application before and after running your main dispatch function. Redux middleware functions have this signiture:

<pre>`middleware:: next -&gt; action -&gt; retVal
`</pre>

It might look something like this:

<pre>`export default function createLogger({getState}){
  return (next) =&gt; 
    (action) =&gt; {
      const console = window.console;
      const prevState = getState();
      const returnValue = next(action);
      const nextState = getState();
      const actionType = String(action.type);
      const message = `action ${actionType}`;

      console.log(`%c prev state`, `color: #9e9e9e`, prevState);
      console.log(`%c action`, `color: #03A9F4`, action);
      console.log(`%c next state`, `color:#4CAF50`, nextState)
    };
}
`</pre>

Notice that createLogger accepts the getState method which is injected by applyMiddleware.js and used inside closure to read the current state. This will return a new function with the `next` parameter which is used to compose the next chained middleware function or the main dispatch function.

This function returns a curried function that accepts the action object which can be read or modified before sending it to the next middleware function in the chain. Finally the main dispatch function is invoked with the action object.
  • First it captures the previous state
  • The action is dispatched to the next middleware function
  • All downstream middleware functions in the chain are invoked
  • The reducer functions in the store are called with the action payload
  • The logger middleware the gets the resulting next state

    Dissecting applyMiddleware.js

    `export default function applyMiddleware(...middlewares){
      return (next) => 
        (reducer, initialState) => {
          var store = next(reducer, initialState);
          var dispatch = store.dispatch;
          var chain = [];
    
          var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
          };
    
          chain = middlewares.map(middleware => middleware(middlewareAPI));
    
          dispatch = compose(...chain, store.dispatch);
    
          return{
            ...store,
            dispatch
          };
        };
    }
    `

    The applyMiddleware function probably could have been named a little better, such as applyMiddlewareToStore.

    The applyMiddleware function returns a function that takes a mysterious next argument:

    `return (next) => (reducer, initialState) => {...}
    `

    The next argument will be a function that is used to create a store. By default you should look at the implementation for createStore. The final returned function will be like createStore and replace the dispatch function with it’s associated middlewares.

    Next we assign the store implementation to the function responsible for creating the new store. Then we create a variable to the store’s original dispatch function. Finally we setup an array to hold the middleware chain we will be creating.

    `var store = next(reducer, initialState)
    var dispatch = store.dispatch
    var chain = [];
    `

    This next bit of code injects the getState function and the original dispatch function from the store into each middleware function which you can optionally use in your middleware(the applyMiddleware function pass getState and dispatch to middlewares in it).

    The resultant middleware is stored in the chain array

    `var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    
    chain = middlewares.map(middleware => middleware({middlewareAPI}))
    `

    Now we create our replacement dispatch function with the information about hte middleware chain:

    `dispatch = compose(...chain, store.dispatch);
    `

    The magic to composing our middleware chain lies in this utility function supplied by Redux. Here is the implementation

    `export default function compose(...funcs){
      return funcs.reduceRight((compsed,f) => f(composed))
    }
    `

    The composing function will literally express your functions as a composition injecting each middleware as an argument to the next middleware in the chain. Order is important here when assembling your middleware function. Finally, the original store dispatch method is composed. This new looks like:

    `middlewareI(middlewareJ(middlewareK(store.dispatch)))(action)
    `

    The final thing to do is return the new store object with the overriden function:

    `return {
      ...store,
      dispatch
    }
    `

    Let’s add our logger middleware we started above into a custom store with the enhanced dispatch function.

    `import { createStore, applyMiddlware } from 'redux'
    import loggerMiddleware from 'logger'
    import rootReducer from './reducers'
    
    const createStoreWithMiddleware = applyMiddleware(loggerMiddleware)(createStore)
    
    export default function configureStore(initialState){
      return createStoreWithMiddleware(rootReducer, initialState);
    }
    
    const store = configureStore()
    `

    Asynchronous Middleware

    Once you get comfortable with the basics of Redux middleware you will likely want to work with asynchronous actions that involve some sort of asynchronous execution. In particular look at ‘redux-thunk’ for more details. Let’s say you have an action creator that has some async functionality to get stock quote information:

    `function fetchQuote(symbol){
      requestQuote(symbol);
      return fetch('......')
             .then(req => req.json())
             .then(json => showCurrentQuote(symbol, json));
    }
    `

    There is no obvious way here to dispatch an action that would be returned from the fetch which is Promise based. Plus we do not have a handle to the dispatch function. Therefore we can use the redux-thunk middleware to defer execution of these operations. By wrapping the execution in a function you can delay this execution:

    `function fetchQuote(symbol){
      return dispatch => {
        dispatch(requestQuote(symbol));
        return fetch('......').then(req=>req.json).then(json => dispatch(showCurrentQuote(symbol,json))
      }
    }
    `

    Remember that the applyMiddleware function will inject the dispatch and the getStat function as parameters int o the redux-thunk middleware. Now you can dispatch the resultant action objects to your store which conatains reducers. Here is the middleware function for redux-thunk that does this for you.

    `export default function thunkMiddleware({dispatch,getState}){
    return (next) =>

    action =&gt; {
      typeof action === 'function'? action(dispatch, getState):next(action)
    }
    

    }

If the action is a function it will be called with the dispatch and getState function..

Connect 相关

预备知识

回顾一下 Redux 基本用法

const reducer = (state = {count: 0}, action ) =&gt; {
  switch(action.type){
  case "INCRESE": return {count: state.count + 1}
  case "DECRESE": return {count: state.count - 1}
  default: return state;
  }
}

const actions = {
  increase: () =&gt; { type: 'INCREASE'},
  decrease: () =&gt; { type: 'DECREASE'}
}

const store = createStore(reducer);

store.subscribe(()=&gt;{
  console.log(store.getState())
});

store.dispatch(action.increase());
`</pre>

通过 reducer 创建一个 store, 每当我们在 store 上 dispatch 一个 action, store 内的数据就会通过 reducer 变化.

我们当然可以直接在 React 中使用 Redux, 在最外层容器中初始化 store, 然后将 state 上的属性作为 props 层层传下去

<pre>`class App extends Component{
  componentWillMount(){
    store.subscribe((state) =&gt; this.setState(state))
  }
  render(){
    return &lt;Comp state={this.state} onIncrease = {()=&gt;store.dispatch(actions.increase())} onDecrease = {()=&gt; store.dispatch(actions.decrease())}
  } /&gt;
}
`</pre>

但这不是最佳方式, 最佳方式是使用 react-redux 提供的 Provider 和 connect 方法

### 使用 React-redux

首先在最外层容器中, 把所有内容包裹在 Provider 组件中, 将之前创建的 store 作为 prop 传递给 Provider

<pre>`const App = () =&gt; {
  return(
    &lt;Provider store = {store}&gt;
      &lt;Comp /&gt;
    &lt;/Provider&gt;
  )
}
`</pre>

Provider 中的任何一个组件(比如这里的 Comp) 如果需要使用 state 中的数据, 就必须是 `被 connect 过的`组件, 使用 connect 方法对你编写的组件(MyComp)进行包装后的产物.

<pre>`class MyComp extends Component {
  // content...
}

const Comp = connect(...args)(MyComp);
`</pre>

可见 connect 是重中之重

### connect 详解

究竟 connect 做了什么

首先看一下函数的签名:

<pre>`connect([mapStatToProps], [mapDispatchToProps], [mergeProps], [options])
`</pre>

connect() 函数接受四个参数, 分别是

<pre>`mapStatToProps, mapDispatchToProps, mergeProps, options
`</pre>

<pre>`mapStatToProps(state, ownProps): stateProps
`</pre>

这个函数允许我们将 store 中的数据作为 props 绑定到组件上

<pre>`const mapStatToProps = (state) =&gt; {
  return {
    count: state.count
  }
}
`</pre>

这个函数的第一个参数就是 Redux 的 store, 我们从中摘取 count 属性, 因为返回了具有 count 属性的对象, 所以 MyComp 会有名为 count 的 props 字段

<pre>`class MyComp extends Component{
  render(){
    return &lt;div&gt;Count: {this.props.count}&lt;/div&gt;
  }
}

const Comp = connect(...args)(MyComp);
`</pre>

当然你不必将 state 原封不动传递给组件, 可以根据 state 中的数据, 动态的输出组件需要的(最小)属性

<pre>`const mapStateToProps = (state) =&gt; {
  return {
    greaterThanFive: state.count &gt; 5
  }
}
`</pre>

第二个参数 ownProps, 是 MyComp 自己的 props, 有时候 ownProps 也会对其产生影响, 比如当你在 store 中维护一个用户列表, 而你的组件 MyComp 只关心一个用户(通过 props 中的 userId体现)

<pre>`const mapStateToProps = (state, ownProps) =&gt; {
  // state 是{ userList:[{id:0,name;'王二'}]}
  return {
    user: _.find(state.userList, {id:ownProps.userId})
  }
}

class MyComp extends Component{
  static PropTypes = {
    userId: PropTypes.string.isRequired,
    user: PropTypes.object
  };
  render(){
    return &lt;div&gt;UserName: {this.props.user.name}&lt;/div&gt;
  }
}

const Comp = connect(mapStateToProps)(MyComp)
`</pre>

当 state 变化, 或者 ownProps 变化时, mapStateToProps 都会被调用, 计算出一个新的 stateProps , 在与 ownProps merge 后, 更新给 MyComp

这就是将 Redux store 中的数据连接到组件的基本方法

mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二个参数是 mapDispatchToProps, 功能是将 action 作为 props 绑定到 MyComp 上

<pre>`const mapDispatchToProps = (dispatch, ownProps) =&gt; {
  return {
    increase: (...args) =&gt; dispatch(actions.increase(...args)),
    decrease: (...args) =&gt; dispatch(actions.decrease(...args))
  }
}

class MyComp extends Component{
  render(){
    const {count, increase, decrease} = this.props
    return (
      &lt;div&gt;
        &lt;div&gt;Count:{count}&lt;/div&gt;
        &lt;button onClick={increase}&gt;+&lt;/button&gt;
        &lt;button onClick={decrease}&gt;-&lt;/button&gt;
      &lt;/div&gt;
    )
  }
}

const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp)
`</pre>

由于 mapDispatchToProps 方法返回了具有 increase 属性和 decrease 属性的对象, 这两个属性也会成为 MyComp 的 props

如上所示, 调用 actions.increase()只能得到一个 action 对象{type:'INCREASE'}, 要触发这个 action 必须要通过 dispatch 调用, dispatch 正式 mapDispatchToProps 的第一个参数, 但是为了不让 MyComp 组件感知到 dispatch 的存在, 我们需要将 increase 和 decrease 方法包装一下, 使之成为直接可以被调用的函数(即触发该方法就会触发 dispatch)

Redux 本身提供了 bindActionCreator 函数, 来将 action 包装成可以直接被调用的函数

<pre>`import {bindActionCreator} from 'redux'
const mapDispatchToProps = (dispatch,ownProps) =&gt; {
  return bindActionCreator({
    increase: actions.increase,
    decrease: actions.decrease
  })
}

同样当 ownProps 变化时, 该函数也会被调用, 生成一个新的 dispatchProps( 在与 stateProps 和 ownPorps merge 后), 更新给 MyComp. 注意 action 的变化不会引起上述过程, 默认 action 在组件的生命周期中是固定的

[mergeProps(stateProps, dispatchProps, ownProps): props]

之前说过不管是 stateProps 还是 dispatchProps, 都需要和 ownProps merge 之后才会被赋给 MyComp, connect 的第三个参数就是来做这个事情的, 通常情况下不需要传这个参数, connect 会使用 Object.assign 方法代替该方法.

其他

最后还有一个 options 参数, 比较简单, 一般也不会用到.

参考

部署React + Redux 的 Web 开发环境

好的部署需要做到两点:
1. 性能优化: 包括代码执行速度, 页面载入速度
2. 自动化: 减少重复工作

使用 React+Redux 的时候往往会用到其调试工具 Redux DevTools, 在手动配置 DevTools 时需要针对 Store 和 Component 进行一些配置, 然而这些都是为了方便调试的, 生产环境下我们不希望加入这些东西, 所以建议从代码上隔离 development 和 production 环境:

containers/
  Root.js
  Root.dev.js
  Root.prod.js
  ...

store/
  index.js
  store.dev.js
  store.prod.js
`</pre>

同时采用单独的入口文件, 比如上面的 containers/Root.js, 按需要加载不同环境的代码

<pre>`if (process.env.NODE_ENV === 'production'){
  module.exports = require('./Root.prod');
} else {
  module.exports = require('./Root.dev');
}
`</pre>

有一个细节需要注意: ES6 语法不支持在 if 中书写 import 语句, 所以这里采用了 CommonJS 的模块引入方法 require.

另一个需要注意的地方是按需要 import, 否则可能在打包的时候引入不必要的代码

使用 webpack 进行打包工作

<pre>`webpack --config webpack.config.prod.js --progress
`</pre>

注意要为不同的环境准备不同的 webpack 配置文件. 比如 `webpack.config.dev.js` 和 `webpack.config.prod.js` . 看一下比较关键的配置选项

### devtools

启用 source-map, 这样 webpack 会生成两个包
- bundle.js
- bundle.js.map
把用于定位源码的 source map 分离出去, 减少 bundle.js 的体积

source-map 只会在浏览器 devtools 激活时加载, 并不会影响正常页面的饿加载速度.

### plugins

<pre>`plugins:[
  new webpack.optimize.UglifyJsPlugin({
    compress:{
      warning: false
    }
  }),
  new webpack.optimize.DedupePlugin(),
  new webpack.optimize.OccurenceOrderPlugin()
]
`</pre>

### 不要忽视 NODE_ENV

NODE_ENV 其实就是一个环境变量, 在 Node 中 可以通过`process.env.NODE_ENV`获取, 目前大家汪汪用这个变量区分当前是 development 还是 production.

通过 webpack 的 DefinePlugin 设置环境变量:

<pre>`plugins:[
  ...
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stingify('production')
  })
]

### 添加 hash
前端公认的 Best Practice 就是给资源打上 hash 标签, 这对缓存静态资源很有用

1\. 给 bundle.js 添加 hash 标签
`</pre>

output{
  path: ...
  filename: 'bundle.[hash].js'
}

<pre>`使用 html-webpack-plugin 这个插件自动生成带有
`</pre>

&lt;script src='bundle.[hash].js>&lt;/script>'的 html 文件

<pre>`配置如下:
`</pre>

plugins:[
  ...
  new HtmlWebpackPlugin({
    title:'html title',
    template:'./template.html',
    favicon: './static/images/logo.ico'
  })
]

<pre>`配置 template.html
`</pre>

<!DOCTYPE html>
&lt;html>
&lt;head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
&lt;/head>
&lt;body>

<div id="react-container"></div>

&lt;/body>
&lt;/html>

<pre>`
如果需要在 React Component 中添加图片, 建议采用
`</pre>

import LOGO.png from './static/images/logo.png'
&lt;img src={LOGO.png} alt="" />

<pre>`使用 import 或 require 可以让 webpack 对图片进行打包

使用 file-loader 可以对图片添加 hash 标签然后返回相应的 URL

module:{
loaders:[
{
test: /.(png|jpg),
loader:”file?name=[hash].[ext]”
}
]
}
`

引入 CSS

可以直接在 js 文件中 require('./foo.css'), 然后使用 style-loader 和 css-loader 进行处理, 最终效果是将 css 嵌入到 HTML 的 style 标签中, 这样就不需要使用 hash

CSS 较大的话, 可以考虑使用 extract-text-webpack-plugin 插件将 CSS 合并后独立为一个文件, 并自定在页面加载的时候添加 link 标签

ImmutableJS Introduction

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presetns a mutative API which does not update the data in-place, but instead always yields new updated data.

Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

These data structures are highly efficient on modern JavaScript VMs by using structural sharing via ‘hash maps tries’ and ‘vector tries’ as popularized by Clojure and Scala, minimizing the need to copy or cache data.

Immutable also provides a lazy Seq, allowing efficient chaining of collection methods like map and filter without creating intermediate representations. Create some Seq with Range and Repeat.

Getting Started

npm install Immutable

var Immutable = require('immutable'); var map1 = Immutable.Map({a:1,b:2,c:3}); var map2 = map1.set('b',50); map1.get('b'); //2 map2.get('b'); //50

The case for Immutability

Much of what makes application development difficult is tracking mutation and maintaining state. Development with immutable data encourages you to think differently about how data flows through your application.

Subscribing to data event throughout your application.

Immutable data never change.

Immutable collections should be treated as value rather than object. While objects represents some thing which could change over time, a value represents the state of that thing at a particular instance of time. This principle is most important to understanding the appropriate use of immutable data. In order to treat Immutable.js collections as values, it’s important to use the Immutable.is() function or .equals() method to determine value equality instead of === operator which determines object reference identity.

var map1 = Immutable.Map({a:1,b:2,c:3}); var map2 = map1.set('b',2); assert(map1.equals(map2) === true); var map3 = map1.set('b',50); assert(map1.equals(map3) === false);

Note: As a performance optimization Immutable attempts to return the existing collection when an operation would result in an identical collection, allowing for using === reference equality to determine if something definitely has not changed. This can be extremely useful when used within memoization function which would prefer to re-run the function if a deeper equality check could potentially be more costly. The === equality check is also used internally by Immutable.js and equals() as a performance as a performance optimization.

If an object is immutable, it can be ‘copied’ simply by marking another reference to it instead of copying the entire object. Because a reference is much smaller than the object itself, this results in memory savings and a potential boost in execution speed for programs which rely on copies(such as an undo-stack).

var map1 = Immutable.Map({'a':1,'b':2,'c':3}); var clone = map1;

JavaScript-first API

While immutable is inspired by Clojure, Scala, Haskell and other functional programming environments, it’s designed to bring these powerful concepts to JavaScript, and therefore has an Object-Oriented API that closely mirrors that of ES6 Array, Map, and Set.

The difference for the immutable collections is that methods which would mutate the collection, like push, set, unshift, or splice instead of return a new immutable collection. Methods which return new array like slice or concat instead return new immutable collections.

var list1 = Immutable.List.of(1,2); var list2 = list1.push(3,4,5); var list3 = list2.unshift(0); var list4 = list1.concat(list2,list3); assert(list1.size === 2); assert(list2.size === 5); assert(list3.size === 6); assert(list4.size === 13); assert(list4.get(0) === 1);

Almost all of the methods on Array will be found in similar form on Immutable.List, those of Map found on Immutable.Map and those of Set found onImmutable.Set, including collection operation like forEach(), and map().

var alpha = Immutable.Map({a:1,b:2,c:3,d:4}); alpha.map((v,k) =&gt; k.toUpperCase()).join(); // 'A,B,C,D'

Accepts raw JavaScript objects

‘immutable’ accepts plain JavaScript Arrays and Objects anywhere a mehtod expects an Iterable with no performance penalty.

var map1 = Immutable.Map({a:1,b:2,c:3,d:4}); var map2 = Immutable.Map({c:10,a:20,t:30}); var obj = {d:100,o:200,g:300}; var map3 = map1.merge(map2,obj); // Map {a:20,b2,c:10,d:100,t:30,o:200,g:300}

Immutable can treat any JavaScript Array or Object as an Iterable. You can take advantage of this in order to get sophisticated collection methods on JavaScript Objects, which otherwise have a very sparse native API.

var myObject = {a:1,b:2,c:3}; Immutable.Seq(myObject).map(x=&gt;x*x).toObject(); // {a:1,b:4,c:9};

Keep in Mind, when using JS objects to construct Immutable Maps, that JS Object properties are always strings, even if written in a quote-less shorthand, while Immutable Map accept keys of any type.

1
2
3
4
5
6
7
8
  var obj = {1: &#039;one&#039;};
Object.keys(obj); // [&#039;1&#039;]
obj[1]; // &#039;one&#039;
obj[&#039;1&#039;]; // &#039;one&#039;

var map = Immutable.fromJS(obj);
map.get(&#039;1&#039;); // &#039;one&#039;
map.get(1); // undefined

Property access for JS Object first converts the key to a string, but since Immutable Map keys can be of any type the argument to get() is not altered.
Namely Immutable Map will treat ‘1’ and 1 differently.

Converts Back to Raw JS Obejcts

All Immutable Iterable can be converted to plain JS Arrays and Objects shallowly with toArray() and toObject() or deeply with toJS(). All Immutable Iterables also implement toJSON() allowing them to be passed to JSON.stringify() directly.

var deep = Immutable.Map({a:1,b:2,c:Immutable.List.of(3,4,5)}); deep.toObject() // {a:1,b:2,c:List [3,4,5]} deep.toArray() // [1,2,List [3,4,5]] deep.toJS() // {a:1,b:2,c:[3,4,5]} JSON.stringify(deep) // '{"a":1,"b","c":[3,4,5]}'

Embraces ES6

Immutable takes advantage of features added to JS ES6, the latest standard version of ECMAScript, including Interators,Arrow Function, Classes, and Modules.

Nested Structures

The collections in immutable are intended to be nested, allowing for deep trees of data, similar to JSON

var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}}) // Map{ a: Map { b: Map { c: List [3,4,5]}}}
A few power-tool allow for reading and operating on nested data. The most useful are mergeDeep, getIn, setIn and updateIn found on List, Map and OrderedMap

1
2
3
4
5
6
7
8
  var nested2 = nested.mergeDeep({a:{b:{d:6}}})
// Map {a: Map { b: { c: List [3,4,5], d: 6}}}

nested2.getIn([&#039;a&#039;,&#039;b&#039;,&#039;d&#039;]); //6

var nested3 = nested2.updateIn([&#039;a&#039;,&#039;b&#039;,&#039;d&#039;], value =&gt; value + 1); d =&gt; 7

var nest4 = nested3.updateIn([&#039;a&#039;,&#039;b&#039;,&#039;c&#039;], list =&gt; list.push(6)); c =&gt; List [3,4,5,6]

Lazy Seq

Seq describes a lazy operation, allowing them to efficiently chain use all the Iterable methods.(such as map and filter)

Seq is immutable – Once a Seq is created it cannot be changed, appended to, rerranged or otherwise modified. Instead, any mutative method called on a Seq will return a new Seq.

Seq is lazy – Seq does as little work as necessary to respond to any method call.

Once the Seq is used, it performs only the work necessary.

Any collection can be converted to a lazy Seq with toSeq().

var seq = Immutable.Map({a:1,b:2,c:3}).toSeq();

Seq allow for the efficient chaining of sequence operations, especially when converting to a different concrete type(such as to a JS Object)

seq.flip().map(k =&gt; k.toUpperCase()).flip().toObject(); // Map {A: 1, B:2, C:3}

Equality treats Collection as Data

Immutable provides equality which treats immutable data structures as pure data, performing a deep equality check if necessary.

var map1 = Immutable.Map({a:1,b:1,c:1}); var map2 = Immutable.Map({a:1,b:1,c:1}); assert(map1 !== map2) // two different instance, not same address assert(Immutable.is(map1,map2)); // have equivalent values assert(map1.equals(map2)); // alternatively use the equals methods.

Immutable.is() uses the same measure of equality as Object.is including if both are immutable and all keys and values are equal using the same measure of equality.

Batching Mutations

Applying a mutation to create a new immutable object results in some overhead, which can add up to a minor performance penalty. If you need to apply a series of mutations locally before returning, Immutable gives you the ability to create a temporary mutable(transient) copy of a collection and apply a batch of mutations in a performance manner by using withMutations. In fact, this is exactly how Immutable applies complex mutations itself.

As an example, building list2 results in the creation of 1 , not 3, new immutable Lists
var list1 = Immutable.List.of(1,2,3); var list2 = list1.withMutations(function(list){ list.push(4).push(5).push(6) }); assert(list1.size === 3); assert(list2.size === 6);

Note: Immutable also provides asMutable and asImmutable, but only encourages their use when withMutations will not suffice. Use caution to not return a mutable copy could result in undesired behavior.

important: Only a select few method can be used in withMutations including set, push, pop. These methods can be applied directly against a persistent data-structure where other methods like map,filter,sortand splice will always return new immutable data-structure and never mutate a mutable collection.

Redux 搭配 React

第一步是初始化 react 组件

getInitialState(){
  return{
    item: store.getState()
  }
}
`</pre>

第二步是在组件 render 后调用 subscribe 函数, 每次 redux 发生 dispatch 的时候都要调用 react 的 setState

<pre>`componentDidMount(){
  var unsubscribe = store.subscribe(this.onChange)
}
onChange(){
  this.setState({
    item: store.getState()
  })
}

React 会根据 State 的变化重新 render 组件
this.state.item.todos.map()

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×