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的配置说明。
import React from 'react' // 引入React
import { render } from 'react-dom' // 引入render方法
import { Provider } from 'react-redux' // 利用Provider可以使我们的 store 能为下面的组件所用
import { Router, browserHistory } from 'react-router' // Browser history 是由 React Router 创建浏览器应用推荐的 history
import { syncHistoryWithStore } from 'react-router-redux' // 利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件 import finalCreateStore from './src/store/configureStore' //引入增强后的store
import DevTools from './src/containers/DevTools' // 引入Redux调试工具DevTools
import reducer from './src/reducers' // 引入reducers集合
import routes from './src/routes' // 引入路由配置 import './assets/css/bootstrap.min.css' // 引入样式文件 // 给增强后的store传入reducer
const store = finalCreateStore(reducer) // 创建一个增强版的history来结合store同步导航事件
const history = syncHistoryWithStore(browserHistory, store) render(
{/* 利用Provider包裹页面 */}
<Provider store={store}>
<div>
{/* 渲染根路由 */}
<Router history={history} routes={routes} />
{/* 渲染调试组件 */}
<DevTools />
</div>
</Provider>,
document.getElementById('mount')
)
在入口文件中我们尽量只需要保留基本的东西,其余的配置代码我们可以放到相应的配置文件中去,比如路由、reducers及store的配置等。这里我都把它们放置到了独立的js中,只在入口文件中通过import引入,这样管理和维护起来会非常方便,但也会相应增加理解的难度,然而一旦上手就会很容易。那么接下来我们再来看下store配置吧。
store配置
import thunk from 'redux-thunk' // redux-thunk 支持 dispatch function,并且可以异步调用它
import createLogger from 'redux-logger' // 利用redux-logger打印日志
import { createStore, applyMiddleware, compose } from 'redux' // 引入redux createStore、中间件及compose
import DevTools from '../containers/DevTools' // 引入DevTools调试组件 // 调用日志打印方法
const loggerMiddleware = createLogger() // 创建一个中间件集合
const middleware = [thunk, loggerMiddleware] // 利用compose增强store,这个 store 与 applyMiddleware 和 redux-devtools 一起使用
const finalCreateStore = compose(
applyMiddleware(...middleware),
DevTools.instrument(),
)(createStore) export default finalCreateStore
这里我们需要了解中间件(Middleware)的概念。middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码,你可以在一个项目中使用多个独立的第三方 middleware,如上面的redux-thunk和redux-logger。详细资料请参考官方文档:
http://cn.redux.js.org/docs/advanced/Mid...
路由配置
上面的入口文件配置中我们把路由配置部分单独放到了routes.js的文件中,这里我们就来看下其配置:
import React from 'react' // 引入react
import { Route, IndexRoute } from 'react-router' // 引入react路由
import { App, Home, Foo, Bar, Antd } from './containers' // 引入各容器组件 export default (
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="index" component={Home}/>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
<Route path="antd" component={Antd}/>
</Route>
)
这里的路由配置和不使用redux时候是一样的,唯一需要了解的是容器组件和展示组件的概念。上面配置文件中的路由加载的组件都可以认为是容器组件。
(1)顾名思义,展示组件包含在容器组件中,只用作页面展示,不会定义数据如何读取如何改变,只通过this.props接受数据和回调函数;
(2)而容器组件中包含各展示组件的数据,即Props,它们为展示组件或其他组件提供数据和方法。
我们应该把它们放在不同的文件夹中,以示区别,如上面“项目目录”中的containers和components文件夹分别存放容器组件和展示组件。具体说明可以参考文章:http://www.jianshu.com/p/6fa2b21f5df3
根组件配置
import React, { Component } from 'react' // 引入React
import { Link } from 'react-router' // 引入Link处理导航跳转 export default class App extends Component {
render() {
return(
<div>
<nav className="navbar navbar-default">
<div className="container-fluid">
<div className="navbar-header">
<span className="navbar-brand" href="#">
<Link to="/">Redux</Link>
</span>
</div>
<ul className="nav navbar-nav">
<li>
<Link to="/index" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>计数器</Link>
</li>
<li>
<Link to="/foo" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>静态数据</Link>
</li>
<li>
<Link to="/bar" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>动态数据</Link>
</li>
<li>
<Link to="/antd" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>结合antd</Link>
</li>
</ul>
</div>
</nav>
<div className="panel panel-default">
<div className="panel-body">
{ this.props.children }
</div>
</div>
</div>
)
}
}
整个根组件App.js主要渲染了整个应用的导航和可变区域,这其实和Redux没有关系。需要注意的是to中的URL地址需要和routes.js中的path地址名称一致。
写到这里还没有介绍Redux中的Action及Reducer的配置,那么接下来就来介绍下。
Action配置
import { INCREASE, DECREASE, GETSUCCESS, REFRESHDATA } from '../constants' // 引入action类型名常量
import 'whatwg-fetch' // 可以引入fetch来进行Ajax // 这里的方法返回一个action对象
export const increase = n => {
return {
type: INCREASE,
amount: n
}
} export const decrease = n => {
return {
type: DECREASE,
amount: n
}
} export const refreshData = () => {
return {
type: REFRESHDATA
}
} export const getSuccess = (json) => {
return {
type: GETSUCCESS,
json
}
} function fetchPosts() {
return dispatch => {
return fetch('data.json')
.then((res) => { console.log(res.status); return res.json() })
.then((data) => {
dispatch(getSuccess(data))
})
.catch((e) => { console.log(e.message) })
}
} // 这里的方法返回一个函数进行异步操作
export function fetchPostsIfNeeded() { // 注意这个函数也接收了 getState() 方法
// 它让你选择接下来 dispatch 什么
return (dispatch, getState) => {
return dispatch(fetchPosts())
}
}
上面返回一个action对象的方法叫做“action 创建函数”,它就是生成action的方法,也是store数据的唯一来源。
上面返回一个函数的方法叫做“异步action”,这里使用的是Redux Thunk middleware,要引入redux-thunk这个专门的库才能使用,这样我们就可以实现异步Ajax请求改变状态等功能了。
Reducer配置
// reducers/count.js
import { INCREASE, DECREASE, GETSUCCESS, REFRESHDATA } from '../constants' // 引入action类型常量名 // 初始化state数据
const initialState = {
number: 1,
lists: [
{text: '整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。'},
{text: '惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。'},
{text: '为了描述 action 如何改变 state tree ,你需要编写 reducers。'},
{text: '就是这样,现在你应该明白 Redux 是怎么回事了。'}
],
data: []
} // 通过dispatch action进入
export default function update(state = initialState, action) { // 根据不同的action type进行state的更新
switch(action.type) {
case INCREASE:
return Object.assign({}, state, { number: state.number + action.amount })
break
case DECREASE:
return Object.assign({}, state, { number: state.number - action.amount })
break
case GETSUCCESS:
return Object.assign({}, state, { data: action.json })
case REFRESHDATA:
return Object.assign({}, state, { data: [] })
default:
return state
}
}
// reducers/index.js
import { combineReducers } from 'redux' // 利用combineReducers 合并reducers
import { routerReducer } from 'react-router-redux' // 将routerReducer一起合并管理
import update from './count' // 引入update这个reducer export default combineReducers({
update,
routing: routerReducer
})
这里我们主要需要了解如何通过combineReducers来合并reducers,同时在进入reducer方法后我们必须返回一个state的处理结果来更新state状态,否则会报错。还需注意的是在合并reducers的时候,需要加上routerReducer这个由“react-router-redux”提供的reducer来管理路由的状态更新。
容器组件
上文提到了容器组件和展示组件的区别和含义,这里我们需要在容器组件使用connect来搭配Redux来进行状态管理,这是很关键的一步。
import React, { Component, PropTypes } from 'react' // 引入React
import { connect } from 'react-redux' // 引入connect
import List from '../components/List' // 引入展示组件List export default class Foo extends Component {
render() { // 通过this.props获取到lists的值
const { lists } = this.props return(
<div>
<ul className="list-group">
{将值传入展示组件}
{ lists.map((e, index) =>
<List text={e.text} key={index}></List>
)}
</ul>
</div>
)
}
} // 验证组件中的参数类型
Foo.propTypes = {
lists: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string.isRequired
}).isRequired).isRequired
} // 获取state中的lists值
const getList = state => {
return {
lists: state.update.lists
}
} // 利用connect将组件与Redux绑定起来
export default connect(getList)(Foo)
在容器组件中我们需要获取state中的初始状态的时候,我们需要使用connect。任何一个从 connect() 包装好的组件都可以得到一个 dispatch 方法作为组件的 props,以及得到全局 state 中所需的任何内容。connect() 的唯一参数是 selector。此方法可以从 Redux store 接收到全局的 state,然后返回组件中需要的 props。详资料请参考文档:http://cn.redux.js.org/docs/basics/Usage...
展示组件
上面的容器组件中引入了一个展示组件List,我们来看下它的代码:
import React, { Component, PropTypes } from 'react' export default class List extends Component {
render() {
return(
<li className="list-group-item">{this.props.text}</li>
)
}
} List.propTypes = {
text: PropTypes.string.isRequired
}
从中我们可以发现,展示组件没有connect的方法,数据是通过this.props来获取的,这样的方式能够是数据的变化清晰可查,便于管理和维护。
demo演示
最后我们来看下这个demo:
整个demo的代码我都上传到了我的github,需要的童鞋可以访问:https://github.com/luozhihao/redux-basic...下载
总结
Redux的知识点繁多,这里只做了大概的介绍,剩下的还需要自己不断的摸索和实践。希望本文能够帮助你了解利用redux构建项目的大体流程。
此致敬礼
Redux状态管理方法与实例的更多相关文章
- 微信小程序里使用 Redux 状态管理
微信小程序里使用 Redux 状态管理 前言 前阵子一直在做小程序开发,采用的是官方给的框架 wepy , 如果还不了解的同学可以去他的官网查阅相关资料学习:不得不说的是,这个框架确相比于传统小程序开 ...
- React + MobX 状态管理入门及实例
前言 现在最热门的前端框架,毫无疑问是React. React是一个状态机,由开始的初始状态,通过与用户的互动,导致状态变化,从而重新渲染UI. 对于小型应用,引入状态管理库是"奢侈的&qu ...
- react+redux状态管理实现排序 合并多个reducer文件
这个demo只有一个reducer 所以合并reducer这个demo用不到 ,但是我写出来这样大家以后可以用到,很好用,管理多个reducer,因为只要用到redux就不会只有一个reducer所以 ...
- react之redux状态管理
1.传统MVC框架的缺陷 模型(model)-视图(view)-控制器(controller)的缩写 V即View视图:用户看到并与之交互的界面. M即Model模型是管理数据:很多业务逻辑都在模型中 ...
- JSP状态管理 及 Cookie实例
HTTP协议的无状态性 无状态是指,当浏览器发送请求给服务器的时候,服务器响应客户端的请求. 但是当同一个浏览器再次发送请求给了服务器的时候,服务器并不知道它就是刚才那个浏览器. 简单地说,就是服务器 ...
- React构建单页应用方法与实例
React作为目前最流行的前端框架之一,其受欢迎程度不容小觑,从这门框架上我们可以学到许多其他前端框架所缺失的东西,也是其创新性所在的地方,比如虚拟DOM.JSX等.那么接下来我们就来学习一下这门框架 ...
- 为了弄懂Flutter的状态管理, 我用10种方法改造了counter app
为了弄懂Flutter的状态管理, 我用10种方法改造了counter app 本文通过改造flutter的counter app, 展示不同的状态管理方法的用法. 可以直接去demo地址看代码: h ...
- ASP.NET状态管理的总结
阅读目录 开始 hidden-input QueryString Cookie ApplicationState ViewState,ControlState Session Profile 各种状态 ...
- vuex状态管理
msvue组件间通信时,若需要改变多组件间共用状态的值.通过简单的组件间传值就会遇到问题.如:子组件只能接收但改变不了父组件的值.由此,vuex的出现就是用作各组件间的状态管理. 简单实例:vuex的 ...
随机推荐
- VC 中与字符串相关的宏 _T、TEXT,_TEXT、L 的作用
CSDN原博文:http://blog.csdn.net/houkai363/article/details/8134787 遇到了:不能将参数 1 从“const char [5]”转换为“LPCT ...
- 浅析JavaScript事件流——冒泡
一.什么是事件冒泡流 我们知道事件流指的是从页面中接受事件的顺序. 为了形象理解事件冒泡,可以想象三军主将诸葛亮,在帐内运筹帷幄,眼观六路耳听八方,这时候前方的战事情况就需要靠传令兵来传达,当第一位传 ...
- JavaScript 函数节流和函数去抖应用场景辨析
概述 也是好久没更新 源码解读,看着房价蹭蹭暴涨,心里也是五味杂陈,对未来充满恐惧和迷茫 ...(敢问一句你们上岸了吗) 言归正传,今天要介绍的是 underscore 中两个重要的方法,函数节流和函 ...
- 【读fastclick源码有感】彻底解决tap“点透”,提升移动端点击响应速度
申明!!!最后发现判断有误,各位读读就好,正在研究中.....尼玛水太深了 前言 近期使用tap事件为老夫带来了这样那样的问题,其中一个问题是解决了点透还需要将原来一个个click变为tap,这样的话 ...
- 来,一起让我们越来越懒,面向CSS、JS未来编程。(9.28已更新)
2016.10.29更新 本文存在大量的错误,仅供参考. 不知不觉在前端领域马上一个年头就要过去了,然而再看看自己的代码,果然够烂,那么为什么代码一直没有用面向对象的思维去写CSS呢?首先有两点:一点 ...
- html+ccs3太阳系行星运转动画之土星有个环,地球有颗小卫星
在上一篇<html+ccs3太阳系行星运转动画>中实现了太阳系八大行星的基本运转动画. 太阳系又何止这些内容,为丰富一下动画,接下来增加“土星环”和“月球”来充盈太阳系动画. 下面是充盈后 ...
- [Asp.net 5] ApplicationBuilder详解
ApplicationBuilder(IApplicationBuilder接口),是OWIN的基础,而且里面都是代理.代理的代理,各种lambda表达式,估计要看这部分代码,很多人得头昏脑涨.今天就 ...
- MongoDB初识
参考: MongoDB资料汇总专题:
- 智能指针unique_ptr的用法
unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...
- javaScript中的小细节-局部作用域中的var
javaScript中var是很神奇的,在局部作用域中,var a = b = c = 1;是不一样的,a为使用var声明的变量,而b和c则是全局下的,此类变量被称为隐式全局变量:var a = 1; ...