Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux的出现,可以让你构建一致化的应用,运行与不同的环境。
要点
- 应用中所有的 state 都以一个对象树的形式存储在一个单一的 store 中;
 
- 唯一改变 store 的办法是触发 action,一个描述发生什么的对象;
 
- 为了描述 action 如何改变 state 树,你需要编写 reducers;  
 
- 为了UI组件 components 状态无关,你需要编写 containers 来负责管理数据和业务逻辑。
 
所以,关于组件部分,明智的做法是在最外层上使用 Redux ,然后通过 Props 方式传值给内部子组件,使得components UI组件仅仅是pure render(纯展示)
containers 与 components 区别对比
 | 
containers(容器组件) | 
components(UI组件) | 
| Location | 
最顶层,路由处理 | 
中间和子组件 | 
| 与Redux联系 | 
是 | 
否 | 
| 读取数据 | 
从 Redux 获取 state | 
从 props 获取数据 | 
| 修改数据 | 
从 Redux 派发 action | 
从 props 调用回调函数 | 
例子
通过一个城市筛选面板来理解 React + Redux 的使用

示例demo:http://blog.giscafer.com/react-demo-list/#/citypanel
源码:https://github.com/giscafer/react-demo-list
编写 React 应用的时候,会有一个大致的开发步骤:
- 构建应用状态树 state 结构
 
- 编写 action (描述已发生事件的普通对象,所有修改 state 的操作都必须通过触发action)
 
- 编写 reducers (描述 action 如何改变 state tree)
 
- 编写 UI组件 components (纯组件,无状态,所有参数通过Props传,可复用性)
 
- 编写 容器组件 containers (用来负责管理数据和业务逻辑,react-redux 链接components)
 
- 通过 createStore 创建store,通过 Provider 包装根组件
 
Action 创建常量和函数
定好state tree后,编写actions
actions.jsx
1 2 3 4 5 6 7 8 9 10 11 
  |  * citypanel actions  */ export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; export function setVisibilityFilter(filter) {     return { type: SET_VISIBILITY_FILTER, filter } } 
  | 
 
Reducers
当应用很大时,可以将它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器
reducer 和 action 是好基友
./reducers/cityList.jsx 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 
  | import { SET_VISIBILITY_FILTER } from '../../actions/citypanel/actions'; import { cityData } from './cityData.js'; const initialState = cityData.filter(item => item['citynum']) console.log(initialState) export default function cityList(state = initialState, action) {     switch (action.type) {         case SET_VISIBILITY_FILTER:             return cityFilter(action.filter)         default:             return state     } } function cityFilter(filter) {     return cityData.map((item) => {         if (filter === '特大' || !filter) {             if (item['citynum']) {                 return item;             }         } else if (item['pinyin'][0].toLocaleUpperCase() === filter && !item['citynum']) {             return item;         } else {             console.log(filter)         }     }) } 
  | 
 
./reducers/index.jsx 组合所有reducers (多个的时候用)
1 2 3 4 5 6 7 8 9 10 11 
  | import { combineReducers } from 'redux' import cityList from './cityList' const rootReducer = combineReducers({     cityList }) export default rootReducer 
  | 
 
UI组件components
宗旨就是pure function
./components/cityList.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
  | import React from 'react'; export default ({cityList = []}) => {     return (         <ul className='city-list'>             {                 cityList.map((city,index) => {                     if(city){                         return <li className='left textCenter' key={index} data-name={city.name}>{city.name}</li>                     }                 })             }         </ul>     ); } 
  | 
 
./components/letterFilter.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
  | import React from 'react'; export default ({onFilterChange}) => {                              let letterArr=['特大','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];     return (         <ul className='city-index'>             {                 letterArr.map((letter,i) => {                     return <li className='left textCenter on_mouseover' key={i} onClick={()=>{ onFilterChange(letter) }}>{letter}</li>                 })             }         </ul>     ); } 
  | 
 
外层组件组合letterFilter与cityList子组件,./components/index.jsx
//此处的函数参数cityList与setVisibilityFilter是由容器组件传输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
  | import React from 'react'; import LetterFilter from './letterFilter'; import CityList from './cityList'; import '../../styles/citypanel/index.less'; import Nav from '../nav/Nav'; export default ({cityList = [], setVisibilityFilter}) => {     return (         <div className='city-panel'>              <Nav />             <CityList cityList={cityList} />             <LetterFilter onFilterChange={setVisibilityFilter} />         </div>     ) } 
  | 
 
容器组件containers
主要是通过react-redux中间件提供的connect方法来链接containers与components,而connect 方法提供了两个方法 mapStateToProps 与 mapDispatchToProps,它们定义了 UI 组件的业务逻辑。前者负责输入逻辑。
mapStateToProps将 state 映射到 UI 组件的参数(Props),mapDispatchToProps负责输出逻辑,即将用户对 UI 组件的操作映射成 Action,也可以通过bindActionCreators方法将action的所有方法绑定到props上。
./containers/App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
  | import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import cityPanelIndex from '../../components/citypanel/index' import * as ctiyActions from '../../actions/citypanel/actions' function mapStateToProps(state){     return {         cityList:state.cityList     } } function mapDispatchToProps(dispatch){     return bindActionCreators(ctiyActions,dispatch); } export default connect(mapStateToProps,mapDispatchToProps)(cityPanelIndex) 
  | 
 
注册store
将 state 和 action 交给 redux 来管理
./stores/createStore.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
  | import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducer from '../reducers/citypanel' const createStoreWithMiddleware = applyMiddleware(   thunk )(createStore) export default function configureStore(initialState) {   const store = createStoreWithMiddleware(reducer, initialState)      if (module.hot) {          module.hot.accept('../reducers/citypanel', () => {       const nextReducer = require('../reducers/citypanel')       store.replaceReducer(nextReducer)     })   }   return store } 
  | 
 
App主文件入口
最终Provider包装主组件(containers)
Main.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
  |  * citypanel主入口  */ import React from 'react' import { Provider } from 'react-redux' import App from '../../containers/citypanel/App' import configureStore from '../../stores/configureStore' const store = configureStore() export default () => {     return (         <Provider store={store}>             <App />         </Provider>     ) } 
  | 
 
一个react + redux 应用完成了
演示:http://blog.giscafer.com/react-demo-list/#/citypanel
源码:https://github.com/giscafer/react-demo-list
(完)
参考链接