Redux 状态管理方法与实例

前端之家收集整理的这篇文章主要介绍了Redux 状态管理方法与实例前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

状态管理是目前构建单页应用中不可或缺的一环,也是值得花时间学习的知识点。React官方推荐我们使用Redux来管理我们的React应用,同时也提供了Redux的文档来供我们学习,中文版地址为http://cn.redux.js.org/index.html

前言

虽然官方文档上说只需几分钟就能上手 Redux,但是我个人认为即便你看个两三天也可能上手不了,因为文档里面的知识点不仅数量较多,而且还艰涩难懂,不结合一些实例来看很难用于实际项目中去。

但是不要担心自己学不会,这不我就给大家带来了这篇干货,也是我学习Redux的心得体验。

如果你对如何构建React单页应用还不了解的可以先移步我的上一篇文章《React 构建单页应用方法与实例》

那么下面我就将介绍如何利用Redux来管理你的React项目了,而这里我主要教你构建的是基于React + Redux + React-Router方法,这也是官方文档里介绍的比较少但是项目中却必备的知识点。

项目目录

首先,一个基于React + Redux + React-Router的项目目录可以按照我下方的图片来构建:

其中assets目录用于存放项目的静态资源,如css/图片等,src目录则用于存放React的组件资源。

入口文件配置

在webpack的配置项中,我们需要一个或多个入口文件,这里我就不展示关于package.json及webpack.config.js的文件配置,最后我会提供整个项目的下载链接供大家参考。这里我主要介绍下入口文件index.js的配置说明。

  1. import React from 'react' // 引入React
  2. import { render } from 'react-dom' // 引入render方法
  3. import { Provider } from 'react-redux' // 利用Provider可以使我们的 store 能为下面的组件所用
  4. import { Router,browserHistory } from 'react-router' // Browser history 是由 React Router 创建浏览器应用推荐的 history
  5. import { syncHistoryWithStore } from 'react-router-redux' // 利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件
  6.  
  7. import finalCreateStore from './src/store/configureStore' //引入增强后的store
  8. import DevTools from './src/containers/DevTools' // 引入Redux调试工具DevTools
  9. import reducer from './src/reducers' // 引入reducers集合
  10. import routes from './src/routes' // 引入路由配置
  11.  
  12. import './assets/css/bootstrap.min.css' // 引入样式文件
  13.  
  14. // 给增强后的store传入reducer
  15. const store = finalCreateStore(reducer)
  16.  
  17. // 创建一个增强版的history来结合store同步导航事件
  18. const history = syncHistoryWithStore(browserHistory,store)
  19.  
  20. render(
  21. {/* 利用Provider包裹页面 */}
  22. <Provider store={store}>
  23. <div>
  24. {/* 渲染根路由 */}
  25. <Router history={history} routes={routes} />
  26. {/* 渲染调试组件 */}
  27. <DevTools />
  28. </div>
  29. </Provider>,document.getElementById('mount')
  30. )

在入口文件中我们尽量只需要保留基本的东西,其余的配置代码我们可以放到相应的配置文件中去,比如路由、reducers及store的配置等。这里我都把它们放置到了独立的js中,只在入口文件中通过import引入,这样管理和维护起来会非常方便,但也会相应增加理解的难度,然而一旦上手就会很容易。那么接下来我们再来看下store配置吧。

store配置

  1. import thunk from 'redux-thunk' // redux-thunk 支持 dispatch function,并且可以异步调用
  2. import createLogger from 'redux-logger' // 利用redux-logger打印日志
  3. import { createStore,applyMiddleware,compose } from 'redux' // 引入redux createStore、中间件及compose
  4. import DevTools from '../containers/DevTools' // 引入DevTools调试组件
  5.  
  6. // 调用日志打印方法
  7. const loggerMiddleware = createLogger()
  8.  
  9. // 创建一个中间件集合
  10. const middleware = [thunk,loggerMiddleware]
  11.  
  12. // 利用compose增强store,这个 store 与 applyMiddleware 和 redux-devtools 一起使用
  13. const finalCreateStore = compose(
  14. applyMiddleware(...middleware),DevTools.instrument(),)(createStore)
  15.  
  16. export default finalCreateStore

这里我们需要了解中间件(Middleware)的概念。middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码,你可以在一个项目中使用多个独立的第三方 middleware,如上面的redux-thunk和redux-logger。详细资料请参考官方文档:http://cn.redux.js.org/docs/advanced/Middleware.html

路由配置

上面的入口文件配置中我们把路由配置部分单独放到了routes.js的文件中,这里我们就来看下其配置:

  1. import React from 'react' // 引入react
  2. import { Route,IndexRoute } from 'react-router' // 引入react路由
  3. import { App,Home,Foo,Bar,Antd } from './containers' // 引入各容器组件
  4.  
  5. export default (
  6. <Route path="/" component={App}>
  7. <IndexRoute component={Home}/>
  8. <Route path="index" component={Home}/>
  9. <Route path="foo" component={Foo}/>
  10. <Route path="bar" component={Bar}/>
  11. <Route path="antd" component={Antd}/>
  12. </Route>
  13. )

这里的路由配置和不使用redux时候是一样的,唯一需要了解的是容器组件展示组件的概念。上面配置文件中的路由加载的组件都可以认为是容器组件。

  1. 顾名思义,展示组件包含在容器组件中,只用作页面展示,不会定义数据如何读取如何改变,只通过this.props接受数据和回调函数

  2. 而容器组件中包含各展示组件的数据,即Props,它们为展示组件或其他组件提供数据和方法

我们应该把它们放在不同的文件夹中,以示区别,如上面“项目目录”中的containers和components文件夹分别存放容器组件和展示组件。具体说明可以参考文章http://www.jianshu.com/p/6fa2b21f5df3

根组件配置

  1. import React,{ Component } from 'react' // 引入React
  2. import { Link } from 'react-router' // 引入Link处理导航跳转
  3.  
  4. export default class App extends Component {
  5. render() {
  6. return(
  7. <div>
  8. <nav className="navbar navbar-default">
  9. <div className="container-fluid">
  10. <div className="navbar-header">
  11. <span className="navbar-brand" href="#">
  12. <Link to="/">Redux</Link>
  13. </span>
  14. </div>
  15. <ul className="nav navbar-nav">
  16. <li>
  17. <Link to="/index" activeStyle={{color: '#555',backgroundColor: '#e7e7e7'}}>计数器</Link>
  18. </li>
  19. <li>
  20. <Link to="/foo" activeStyle={{color: '#555',backgroundColor: '#e7e7e7'}}>静态数据</Link>
  21. </li>
  22. <li>
  23. <Link to="/bar" activeStyle={{color: '#555',backgroundColor: '#e7e7e7'}}>动态数据</Link>
  24. </li>
  25. <li>
  26. <Link to="/antd" activeStyle={{color: '#555',backgroundColor: '#e7e7e7'}}>结合antd</Link>
  27. </li>
  28. </ul>
  29. </div>
  30. </nav>
  31. <div className="panel panel-default">
  32. <div className="panel-body">
  33. { this.props.children }
  34. </div>
  35. </div>
  36. </div>
  37. )
  38. }
  39. }

整个根组件App.js主要渲染了整个应用的导航和可变区域,这其实和Redux没有关系。需要注意的是to中的URL地址需要和routes.js中的path地址名称一致。

写到这里还没有介绍Redux中的Action及Reducer的配置,那么接下来就来介绍下。

Action配置

  1. import { INCREASE,DECREASE,GETSUCCESS,REFRESHDATA } from '../constants' // 引入action类型名常量
  2. import 'whatwg-fetch' // 可以引入fetch来进行Ajax
  3.  
  4. // 这里的方法返回一个action对象
  5. export const increase = n => {
  6. return {
  7. type: INCREASE,amount: n
  8. }
  9. }
  10.  
  11. export const decrease = n => {
  12. return {
  13. type: DECREASE,amount: n
  14. }
  15. }
  16.  
  17. export const refreshData = () => {
  18. return {
  19. type: REFRESHDATA
  20. }
  21. }
  22.  
  23. export const getSuccess = (json) => {
  24. return {
  25. type: GETSUCCESS,json
  26. }
  27. }
  28.  
  29. function fetchPosts() {
  30. return dispatch => {
  31. return fetch('data.json')
  32. .then((res) => { console.log(res.status); return res.json() })
  33. .then((data) => {
  34. dispatch(getSuccess(data))
  35. })
  36. .catch((e) => { console.log(e.message) })
  37. }
  38. }
  39.  
  40. // 这里的方法返回一个函数进行异步操作
  41. export function fetchPostsIfNeeded() {
  42.  
  43. // 注意这个函数也接收了 getState() 方法
  44. // 它让你选择接下来 dispatch 什么
  45. return (dispatch,getState) => {
  46. return dispatch(fetchPosts())
  47. }
  48. }

上面返回一个action对象的方法叫做“action 创建函数”,它就是生成action的方法,也是store数据的唯一来源。

上面返回一个函数方法叫做“异步action”,这里使用的是Redux Thunk middleware,要引入redux-thunk这个专门的库才能使用,这样我们就可以实现异步Ajax请求改变状态等功能了。

Reducer配置

  1. // reducers/count.js
  2. import { INCREASE,REFRESHDATA } from '../constants' // 引入action类型常量名
  3.  
  4. // 初始化state数据
  5. const initialState = {
  6. number: 1,lists: [
  7. {text: '整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。'},{text: '惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。'},{text: '为了描述 action 如何改变 state tree ,你需要编写 reducers。'},{text: '就是这样,现在你应该明白 Redux 是怎么回事了。'}
  8. ],data: []
  9. }
  10.  
  11. // 通过dispatch action进入
  12. export default function update(state = initialState,action) {
  13.  
  14. // 根据不同的action type进行state的更新
  15. switch(action.type) {
  16. case INCREASE:
  17. return Object.assign({},state,{ number: state.number + action.amount })
  18. break
  19. case DECREASE:
  20. return Object.assign({},{ number: state.number - action.amount })
  21. break
  22. case GETSUCCESS:
  23. return Object.assign({},{ data: action.json })
  24. case REFRESHDATA:
  25. return Object.assign({},{ data: [] })
  26. default:
  27. return state
  28. }
  29. }
  1. // reducers/index.js
  2. import { combineReducers } from 'redux' // 利用combineReducers 合并reducers
  3. import { routerReducer } from 'react-router-redux' // 将routerReducer一起合并管理
  4. import update from './count' // 引入update这个reducer
  5.  
  6. export default combineReducers({
  7. update,routing: routerReducer
  8. })

这里我们主要需要了解如何通过combineReducers来合并reducers,同时在进入reducer方法后我们必须返回一个state的处理结果来更新state状态,否则会报错。还需注意的是在合并reducers的时候,需要加上routerReducer这个由“react-router-redux”提供的reducer来管理路由的状态更新。

容器组件

上文提到了容器组件和展示组件的区别和含义,这里我们需要在容器组件使用connect来搭配Redux来进行状态管理,这是很关键的一步。

  1. import React,{ Component,PropTypes } from 'react' // 引入React
  2. import { connect } from 'react-redux' // 引入connect
  3. import List from '../components/List' // 引入展示组件List
  4.  
  5. export default class Foo extends Component {
  6. render() {
  7. // 通过this.props获取到lists的值
  8. const { lists } = this.props
  9.  
  10. return(
  11. <div>
  12. <ul className="list-group">
  13. {将值传入展示组件}
  14. { lists.map((e,index) =>
  15. <List text={e.text} key={index}></List>
  16. )}
  17. </ul>
  18. </div>
  19. )
  20. }
  21. }
  22.  
  23. // 验证组件中的参数类型
  24. Foo.propTypes = {
  25. lists: PropTypes.arrayOf(PropTypes.shape({
  26. text: PropTypes.string.isrequired
  27. }).isrequired).isrequired
  28. }
  29.  
  30. // 获取state中的lists值
  31. const getList = state => {
  32. return {
  33. lists: state.update.lists
  34. }
  35. }
  36.  
  37. // 利用connect将组件与Redux绑定起来
  38. export default connect(getList)(Foo)

在容器组件中我们需要获取state中的初始状态的时候,我们需要使用connect。任何一个从 connect() 包装好的组件都可以得到一个 dispatch 方法作为组件的 props,以及得到全局 state 中所需的任何内容。connect() 的唯一参数是 selector。此方法可以从 Redux store 接收到全局的 state,然后返回组件中需要的 props。详资料请参考文档:http://cn.redux.js.org/docs/basics/UsageWithReact.html

展示组件

上面的容器组件中引入了一个展示组件List,我们来看下它的代码

  1. import React,PropTypes } from 'react'
  2.  
  3. export default class List extends Component {
  4. render() {
  5. return(
  6. <li className="list-group-item">{this.props.text}</li>
  7. )
  8. }
  9. }
  10.  
  11. List.propTypes = {
  12. text: PropTypes.string.isrequired
  13. }

从中我们可以发现,展示组件没有connect的方法,数据是通过this.props来获取的,这样的方式能够是数据的变化清晰可查,便于管理和维护。

demo演示

最后我们来看下这个demo:

整个demo的代码我都上传到了我的github,需要的童鞋可以访问:https://github.com/luozhihao/redux-basic-example下载。

总结

Redux的知识点繁多,这里只做了大概的介绍,剩下的还需要自己不断的摸索和实践。希望本文能够帮助你了解利用redux构建项目的大体流程。

此致敬礼

本文地址:http://www.jb51.cc/article/p-edfxbjaz-c.html
博客园:http://www.cnblogs.com/luozhihao/p/5660496.html

猜你在找的React相关文章