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
(完)
参考链接