{"action":"created","issue":{"url":"https://api.github.com/repos/matthew-sun/blog/issues/18","repository_url":"https://api.github.com/repos/matthew-sun/blog","labels_url":"https://api.github.com/repos/matthew-sun/blog/issues/18/labels{/name}","comments_url":"https://api.github.com/repos/matthew-sun/blog/issues/18/comments","events_url":"https://api.github.com/repos/matthew-sun/blog/issues/18/events","html_url":"https://github.com/matthew-sun/blog/issues/18","id":103905813,"number":18,"title":"使用Redux管理你的React应用","user":{"login":"matthew-sun","id":5277952,"avatar_url":"https://avatars.githubusercontent.com/u/5277952?v=3","gravatar_id":"","url":"https://api.github.com/users/matthew-sun","html_url":"https://github.com/matthew-sun","followers_url":"https://api.github.com/users/matthew-sun/followers","following_url":"https://api.github.com/users/matthew-sun/following{/other_user}","gists_url":"https://api.github.com/users/matthew-sun/gists{/gist_id}","starred_url":"https://api.github.com/users/matthew-sun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matthew-sun/subscriptions","organizations_url":"https://api.github.com/users/matthew-sun/orgs","repos_url":"https://api.github.com/users/matthew-sun/repos","events_url":"https://api.github.com/users/matthew-sun/events{/privacy}","received_events_url":"https://api.github.com/users/matthew-sun/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":46,"created_at":"2015-08-30T07:29:11Z","updated_at":"2016-04-11T03:24:09Z","closed_at":null,"body":"> React是最好的前端库，因为其发源于世界上最好的后端语言框架。  ---信仰\r\n> \r\n> 4.0 will likely be the last major release. Use Redux instead. It's really great.  —Flummox框架作者  acdliteAndrew Clark\r\n\r\n#### 为什么使用React还需要使用别的框架来搭配？\r\n\r\nReact的核心是使用组件定义界面的表现，是一个View层的前端库，那么在使用React的时候我们通常还需要一套机制去管理组件与组件之间，组件与数据模型之间的通信。\r\n\r\n#### 为什么使用Redux？\r\n\r\nFacebook官方提出了FLUX思想管理数据流，同时也给出了自己的[实现](https://github.com/facebook/flux)来管理React应用。可是当我打开[FLUX](https://github.com/facebook/flux)的文档时候，繁琐的实现，又臭又长的文档，实在难以让我有使用它的欲望。幸好，社区中和我有类似想法的不在少数，github上也涌现了一批关于实现FLUX的框架，比较出名的有[Redux](https://github.com/rackt/redux),[Reflux](https://github.com/reflux/refluxjs),[Flummox](https://github.com/acdlite/flummox)。\r\n\r\n其中Redux的简单和有趣的编程体验是最吸引我的地方。 \r\n\r\n- 简单。和其它的FLUX实现不一样，Redux只有唯一的state树，不管项目变的有多复杂，我也仅仅只需要管理一个State树。可能你会有疑问，一个state树就够用了？这个state树该有多大？别着急，Redux中的Reducer机制可以解决这个问题。\r\n  \r\n- 有趣。忙于迭代项目的你，体会编程带来的趣味是有多久没有体会到了？瞧下面这张图，右边那个调试工具是啥？整个应用的action和state都这么被轻松的管理了？行为还能被保存，删除，回滚，重置？修改了代码，页面不刷新也能产生变化？别开玩笑了，不行，世界那么大，让我去试试！\r\n  \r\n![Redux DevTools](https://camo.githubusercontent.com/a0d66cf145fe35cbe5fb341494b04f277d5d85dd/687474703a2f2f692e696d6775722e636f6d2f4a34476557304d2e676966)\r\n  \r\n注：Redux开发调试工具：[redux-devtools](https://github.com/gaearon/redux-devtools)\r\nReact应用无刷新保存工具：[react-transform](https://github.com/gaearon/react-transform-boilerplate)\r\n  \r\n##### 不明真相的群众，可能这里需要我来安利一下Flux数据流的思想，看图：\r\n  \r\n``` \r\n  ╔═════════╗       ╔════════╗       ╔═════════════════╗\r\n  ║ Actions ║──────>║ Stores ║──────>║ View Components ║\r\n  ╚═════════╝       ╚════════╝       ╚═════════════════╝\r\n       ^                                      │\r\n       └──────────────────────────────────────┘\r\n  \r\n  注意：图片仅仅是FLUX思想，而不是Facebook的实现。\r\n```\r\n  \r\n大致的过程是这样的，View层不能直接对state进行操作，而需要依赖Actions派发指令来告知Store修改状态，Store接收Actions指令后发生相应的改变，View层同时跟着Store的变化而变化。\r\n\r\n举个例子：A组件要使B组件发生变化。首先，A组件需要执行一个Action，告知绑定B组件的Store发生变化，Store接收到派发的指令后改变，那相应的B组件的视图也就发生了改变。假如C，D，E，F组件绑定了和B组件相同的Store，那么C，D，E，F也会跟着变化。\r\n  \r\n### 使用React和Redux开发一个小程序\r\n  \r\n为了更好的描述怎么样使用Redux管理React应用，我做了一个Manage Items的小例子。你可以在这里找到全部的源代码：https://github.com/matthew-sun/redux-example\r\n  \r\n![Manage Items](http://matthew-sun.github.io/images/manage-items.gif)\r\n  \r\n##### 快速查看\r\n  \r\n`1.git clone git@github.com:matthew-sun/redux-example.git`\r\n  \r\n`2.npm install && npm start`\r\n  \r\n`3.open localhost:3000`\r\n\r\n#### 目录结构\r\n\r\n```\r\n.\r\n+-- app\r\n|   +-- actions\r\n|       +-- index.js\r\n|   +-- components\r\n|       +-- content.js\r\n|       +-- footer.js\r\n|       +-- searchBar.js\r\n|   +-- constants\r\n|       +-- ActionTypes.js\r\n|   +-- containers\r\n|       +-- App.js\r\n|   +-- reducers\r\n|       +-- index.js\r\n|       +-- items.js\r\n|       +-- filter.js\r\n|   +-- utils\r\n|   +-- configureStore.js\r\n|   +-- index.js\r\n+-- scss\r\n|   +-- pure.scss\r\n+-- index.html\r\n```  \r\n#### Index.js\r\n  \r\n在入口文件中，我们需要把App和redux建立起联系。Provider是react-redux提供的组件，它的作用是把store和视图绑定在了一起，这里的Store就是那个唯一的State树。当Store发生改变的时候，整个App就可以作出对应的变化。这里的<App />会传进Provider的props.children里。\r\n  \r\n``` javascript\r\n/* app/index.js */\r\n  \r\nimport React from 'react';\r\nimport { Provider } from 'react-redux';\r\nimport App from './containers/App';\r\nimport configureStore from './configureStore';\r\n  \r\nconst store = configureStore();\r\n  \r\nReact.render(\r\n    <div>\r\n        <Provider store={store}>\r\n            <App />\r\n        </Provider>\r\n    </div>,\r\n    document.getElementById('app'));\r\n```\r\n  \r\n#### Constants\r\n\r\nkeyMirror这个方法非常的有用，它可以帮助我们轻松创建与键值key相等的常量。\r\n\r\n``` javascript\r\n/* app/constants/actionTypes.js */\r\n\r\nimport keyMirror from 'fbjs/lib/keyMirror';\r\n  \r\nexport default keyMirror({\r\n    ADD_ITEM: null,\r\n    DELETE_ITEM: null,\r\n    DELETE_ALL: null,\r\n    FILTER_ITEM: null\r\n});\r\n  \r\n// 等于\r\n// export const ADD_ITEM = 'ADD_ITEM';\r\n// export const DELETE_ITEM = 'DELETE_ITEM';\r\n// export const DELETE_ALL = 'DELETE_ALL';\r\n// export const FILTER_ITEM = 'FILTER_ITEM';\r\n  ```\r\n  \r\n#### Actions\r\n  \r\nAction向store派发指令，action 函数会返回一个带有 type 属性的 Javascript Plain Object，store将会根据不同的action.type来执行相应的方法。addItem函数的异步操作我使用了一点小技巧，使用[redux-thunk](https://github.com/gaearon/redux-thunk)中间件去改变dispatch，dispatch是在View层中用bindActionCreators绑定的。使用这个改变的dispatch我们可以向store发送异步的指令。比如说，可以在action中放入向服务端的请求(ajax)，也强烈推荐这样去做。\r\n  \r\n``` javascript\r\n/* app/actions/index.js */\r\n  \r\nimport { ADD_ITEM, DELETE_ITEM, DELETE_ALL, FILTER_ITEM } from '../constants/actionTypes';\r\n  \r\nexport function addItem(item) {\r\n    return dispatch => {\r\n       setTimeout(() => dispatch({type: ADD_ITEM}), 1000)\r\n    }\r\n}\r\nexport function deleteItem(item, e) {\r\n    return {\r\n       type: DELETE_ITEM,\r\n       item\r\n    }\r\n}\r\nexport function deleteAll() {\r\n    return {\r\n       type: DELETE_ALL\r\n    }\r\n}\r\nexport function filterItem(e) {\r\n    let filterItem = e.target.value;\r\n    return {\r\n       type: FILTER_ITEM,\r\n       filterItem\r\n    }\r\n}\r\n```\r\n  \r\n#### Reducers\r\n  \r\nRedux有且只有一个State状态树，为了避免这个状态树变得越来越复杂，Redux通过 Reducers来负责管理整个应用的State树，而Reducers可以被分成一个个Reducer。\r\n  \r\nReduce在javascript Array的方法中出现过，只是不太常用。简单快速的用代码样例来回顾一下：\r\n  \r\n``` javascript\r\n  /* Array.prototype.reduce */\r\n  \r\nvar arr = [1,2,3,4];\r\nvar initialValue = 5;\r\nvar result = arr.reduce(function(previousValue, currentValue) {\r\n    return previousValue + currentValue\r\n}, initialValue)\r\nconsole.log(result)\r\n// 15\r\n// 该回调函数的返回值为累积结果，并且此返回值在下一次调用该回调函数时作为参数提供。\r\n// 整个函数执行的过程大致是这样 ((((5+1)+2)+3)+4)\r\n```\r\n  \r\n回到Redux中来看，整个的状态就相当于从[初始状态]merge一个[action.state]从而得到一个新的状态，随着action的不断传入，不断的得到新的状态的过程。(previousState, action) => newState，注意：任何情况下都不要改变previousState，因为这样View层在比较State的改变时只需要简单比较即可，而避免了深度循环比较。Reducer的数据结构我们可以用[immutable-js](https://github.com/facebook/immutable-js/)，这样我们在View层只需要[react-immutable-render-mixin](https://github.com/jurassix/react-immutable-render-mixin)插件就可以轻松的跳过更新那些state没有发生改变的组件子树。\r\n  \r\n``` javascript\r\n/* app/reducers/items.js */\r\n  \r\nimport Immutable from 'immutable';\r\nimport { ADD_ITEM, DELETE_ITEM, DELETE_ALL } from '../constants/actionTypes';\r\n  \r\nconst initialItems = Immutable.List([1,2,3]);\r\n  \r\nexport default function items(state = initialItems, action) {\r\n    switch(action.type) {\r\n        case ADD_ITEM:\r\n            return state.push( state.size !=0 ? state.get(-1)+1 : 1 );\r\n        case DELETE_ITEM: \r\n            return state.delete( state.indexOf(action.item) );\r\n        case DELETE_ALL:\r\n            return state.clear();\r\n        default:\r\n            return state;\r\n    }\r\n}\r\n```\r\n  \r\n##### 连接reducers\r\n  \r\nRedux提供的combineReducers函数可以帮助我们把reducer组合在一起，这样我们就可以把Reducers拆分成一个个小的Reducer来管理Store了。\r\n  \r\n``` javascript\r\n/* app/reducers/index.js */\r\n  \r\nimport { combineReducers } from 'redux';\r\nimport items from './items';\r\nimport filter from './filter';\r\n  \r\nconst rootReducer = combineReducers({\r\n  items,\r\n  filter\r\n});\r\n  \r\nexport default rootReducer;\r\n  ```\r\n  \r\n#### Middleware\r\n  \r\n在Redux中，Middleware 主要是负责改变Store中的dispatch方法，从而能处理不同类型的 action 输入，得到最终的 Javascript Plain Object 形式的 action 对象。\r\n  \r\n以[redux-thunk](https://github.com/gaearon/redux-thunk)为例子：\r\n  \r\n``` javascript\r\n/* redux-thunk */  \r\nexport default function thunkMiddleware({ dispatch, getState }) {\r\n  return next => \r\n     action => \r\n       typeof action === ‘function’ ? \r\n         action(dispatch, getState) : \r\n         next(action);\r\n}\r\n```\r\n  \r\n当ThunkMiddleware 判断action传入的是一个函数，就会为该thunk函数补齐dispatch和getState参数，否则，就调用next(action)，给后续的Middleware（Middleware 插件可以被绑定多个）得到使用dispatch的机会。\r\n  \r\n``` javascript\r\n /* app/configureStore.js */\r\n  \r\nimport { compose, createStore, applyMiddleware } from 'redux';\r\nimport thunk from 'redux-thunk';\r\nimport rootReducer from './reducers';\r\n  \r\nvar buildStore = compose(applyMiddleware(thunk))(createStore);\r\nexport default function configureStore(initialState) {\r\n  const store = buildStore(rootReducer, initialState);\r\n  return store;\r\n}\r\n```\r\n  \r\n#### UI\r\n  \r\n智能组件和木偶组件，因为本文主要是介绍Redux，对这个感兴趣的同学可以看一下这篇文章[Smart and Dumb Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)。本项目中在结构上会把智能组件放在containers中，木偶组件放于components中。\r\n  \r\n##### containers\r\n  \r\n智能组件，会通过react-redux函数提供的connect函数把state和actions转换为旗下木偶组件所需要的props。\r\n  \r\n``` javascript\r\n/* app/containers/App.js */\r\n  \r\nimport React from 'react';\r\nimport SearchBar from '../components/searchBar';\r\nimport Content from '../components/content';\r\nimport Footer from '../components/footer';\r\nimport { connect } from 'react-redux';\r\nimport ImmutableRenderMixin from 'react-immutable-render-mixin';\r\nimport * as ItemsActions from '../actions';\r\nimport { bindActionCreators } from 'redux';\r\n  \r\nlet App = React.createClass({\r\n     mixins: [ImmutableRenderMixin],\r\n     propTypes: {\r\n         items: React.PropTypes.object,\r\n         filter: React.PropTypes.string\r\n     },\r\n     render() {\r\n         let styles = {\r\n             width: '200px',\r\n             margin: '30px auto 0'\r\n         }\r\n         const actions = this.props.actions;\r\n         return (\r\n             <div style={styles}>\r\n                 <h2>Manage Items</h2>\r\n                 <SearchBar filterItem={actions.filterItem}/>\r\n                 <Content items={this.props.items} filter={this.props.filter} deleteItem={actions.deleteItem}/>\r\n                 <Footer addItem={actions.addItem} deleteAll={actions.deleteAll}/>\r\n             </div>\r\n         )\r\n     }\r\n })\r\n  \r\nexport default connect(state => ({\r\n     items: state.items,\r\n     filter: state.filter\r\n}), dispatch => ({\r\n     actions: bindActionCreators(ItemsActions, dispatch)\r\n}))(App);\r\n  ```\r\n  \r\n##### components\r\n  \r\n木偶组件，各司其职，没有什么关于actions和stores的依赖，拿出项目中也可独立使用，甚至可以和别的actions，stores进行绑定。\r\n  \r\n  - SearchBar：查找Item。\r\n  - Content：控制Items的显示，删除一个Item。\r\n  - Footer：新增Item，删除全部Item。\r\n  \r\n#### 调试工具\r\n  \r\n使用[redux-devtools](https://github.com/gaearon/redux-devtools)调试，为你在开发过程中带来乐趣。\r\n  \r\n``` javascript\r\n/* app/index.js */\r\n  \r\nfunction renderDevTools(store) {\r\n  if (__DEBUG__) {\r\n    let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react');\r\n    return (\r\n      <DebugPanel top right bottom>\r\n        <DevTools store={store} monitor={LogMonitor} />\r\n      </DebugPanel>\r\n    );\r\n  }else {\r\n    return null;\r\n  }\r\n}\r\n  \r\nReact.render(\r\n    <div>\r\n        <Provider store={store}>\r\n            <App />\r\n        </Provider>\r\n        {renderDevTools(store)}\r\n    </div>,\r\n  document.getElementById('app'));\r\n  ```\r\n  \r\n``` javascript\r\n/* app/configureStore.js */\r\n  \r\nimport { compose, createStore, applyMiddleware } from 'redux';\r\nimport thunk from 'redux-thunk';\r\nimport rootReducer from './reducers';\r\n\r\nvar buildStore;\r\n\r\nif(__DEBUG__) {\r\n    buildStore = compose(\r\n      applyMiddleware(thunk),\r\n      require('redux-devtools').devTools(),\r\n      require('redux-devtools').persistState(window.location.href.match(/[?&]debug_session=([^&]+)\\b/))\r\n    )(createStore)\r\n}else {\r\n    buildStore = compose(applyMiddleware(thunk))(createStore)\r\n}\r\n\r\nexport default function configureStore(initialState) {\r\n  const store = buildStore(rootReducer, initialState);\r\n\r\n  if(module.hot) {\r\n    module.hot.accept('./reducers', () => {\r\n      store.replaceReducer(require('./reducers'));\r\n    });\r\n  }\r\n\r\n  return store;\r\n}\r\n```\r\n  \r\n  在你的代码中加上上面的两段代码，运行npm run debug命令，就可以用调试工具来管理你的项目了。\r\n  \r\n#### 延伸阅读\r\n  \r\n  - [Redux Document](http://rackt.github.io/redux/index.html)\r\n  - [Awesome-redux](https://github.com/xgrommx/awesome-redux)\r\n  \r\n#### 写在最后\r\n  \r\n刚接触到Redux和React技术的时候，我几乎是夜夜难以入眠的，技术革新带来的新的思想总是不断的刺激着我的大脑。非常建议你也能来试试Redux，体会我在开发中得到的这种幸福感。\r\n  \r\n如果有任何想要了解的，欢迎来我的[github](https://github.com/matthew-sun)和我一起互动交流。​"},"comment":{"url":"https://api.github.com/repos/matthew-sun/blog/issues/comments/208145842","html_url":"https://github.com/matthew-sun/blog/issues/18#issuecomment-208145842","issue_url":"https://api.github.com/repos/matthew-sun/blog/issues/18","id":208145842,"user":{"login":"matthew-sun","id":5277952,"avatar_url":"https://avatars.githubusercontent.com/u/5277952?v=3","gravatar_id":"","url":"https://api.github.com/users/matthew-sun","html_url":"https://github.com/matthew-sun","followers_url":"https://api.github.com/users/matthew-sun/followers","following_url":"https://api.github.com/users/matthew-sun/following{/other_user}","gists_url":"https://api.github.com/users/matthew-sun/gists{/gist_id}","starred_url":"https://api.github.com/users/matthew-sun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matthew-sun/subscriptions","organizations_url":"https://api.github.com/users/matthew-sun/orgs","repos_url":"https://api.github.com/users/matthew-sun/repos","events_url":"https://api.github.com/users/matthew-sun/events{/privacy}","received_events_url":"https://api.github.com/users/matthew-sun/received_events","type":"User","site_admin":false},"created_at":"2016-04-11T03:24:09Z","updated_at":"2016-04-11T03:24:09Z","body":"@laixstx 用webpack --config webpack.config.min.js命令可以打包文件在本地，然后你再发布到生产环境上面"}}