redux是上手react开发的必经之路,也是目前react项目中使用的最流行状态管理库。虽然咱们不使用redux也能够经过react的state和父子props进行基本的数据通讯和项目开发,可是对于一个大型项目而言,每每考虑的更多的是代码结构和组件之间的通讯,咱们须要一种很优雅且有利于扩展的方式去开发咱们的复杂系统,因此这种状况下使用redux是最佳的选择。javascript
因为以前有朋友但愿快速上手一下redux开发,因此笔者特地开发了一个小项目,但愿经过这个项目可让你们快速掌握redux及其生态的使用方式,以便从此在技术选型上有更多的空间。css
redux的设计思想核心就是把web应用看成一个状态机,视图和状态一一对应,全部的状态都保存在一个对象里。前端
由上图能够看出redux几个核心api就是vue
咱们只要理清它们的关系和工做机制,redux也就能轻松使用了。java
redux的基本工做流程熟悉以后,咱们来看看如何将redux运用在项目中。如下是使用redux的基本步骤,你们能够参考一下:node
基本代码以下:react
// 1. 定义初始化的state
const initSate = {
num: 0
}
// 2. 定义action
function add() {
return {
type: 'INCREMENT'
}
}
function dec() {
return {
type: 'DECREMENT'
}
}
// 3. 编写reducer函数
const reducer = (state = initState, action) => {
switch (action.type) {
case 'INCREMENT': return {...state, {num: state.num + 1}}
case 'DECREMENT': return {...state, {num: state.num - 1}}
default: return state;
}
}
// 建立store
const store = createStore(reducer)
// 4. 使用dispatch触发action
const renderView = () => {
ReactDOM.render(
<YourComponent value={store.getState()} add={() => store.dispatch(add())} dec={() => store.dispatch(dec())} />, document.getElementById('root') ); }; renderView(); store.subscribe(renderView); 复制代码
经过以上的步骤咱们就能够基本开始redux开发了,redux还提供了中间件机制,暴露了applyMiddleware, compose等API,这里咱们先简单提一下,后续会涉及到相关的使用。webpack
实际项目中咱们每每不会直接使用redux,咱们会搭配使用react-redux等库,经过将react和redux以更优雅的方式结合到一块儿来开发更加可维护的项目。css3
react-redux的核心思想是将全部组件分红渲染组件(纯组件)和容器组件(负责处理业务逻辑和状态),渲染组件只负责展现,没有状态,容器组件负责处理各类状态和逻辑。因此用户只须要提供渲染组件来呈现视图,容器组件会由react-redux自动生成。因此整个过程看上去像这样: web
import { connect } from 'react-redux'
const HomeContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Home)
复制代码
home是咱们的UI组件,经过mapStateToProps, mapDispatchToProps这两个函数参数,咱们能够将redux的store和action映射到UI组件的props上,这样咱们就能够实现正常的数据单向流转。
mapStateToProps的做用就是创建一个从(外部的)state对象到(UI 组件的)props对象的映射关系,咱们通常能够这么定义:
const mapStateToProps = (state) => {
let { capacity } = state
return { capacity }
}
复制代码
返回的capacity就是咱们要传给某个UI组件的props里的某个属性。
mapDispatchToProps用来创建 UI 组件的参数到store.dispatch方法的映射,咱们能够这么定义它:
const mapDispatchToProps = dispatch => {
return {
createTodo(data, cb) {
dispatch(createTodo(data, cb))
},
editTodo(data, cb) {
dispatch(editTodo(data, cb))
}
}
}
复制代码
那么用户就能够在props中拿到createTodo,editTodo这两个方法来触发store对应的action。
固然若是只使用以上几种方式咱们仍是不能将state传递给容器组件,咱们须要react-redux提供的Provider组件,它可让容器组件拿到state。
import { Provider } from 'react-redux'
// ...
render(
<Provider store={store}> <Container /> </Provider>,
document.getElementById('root')
)
复制代码
因此完整的用法应该长这样:
// Container.jsx
import React from 'react'
import { connect } from 'react-redux'
import { createTodo, editTodo } from 'store/actions'
const mapStateToProps = (state) => {
let { capacity } = state
return { capacity }
}
const mapDispatchToProps = dispatch => {
return {
createTodo(data, cb) {
dispatch(createTodo(data, cb))
},
editTodo(data, cb) {
dispatch(editTodo(data, cb))
}
}
}
class Home extends React.Component {
// ...
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
// index.js
import Container from './Container'
import { Provider } from 'react-redux'
// ...
render(
<Provider store={store}> <Container /> </Provider>,
document.getElementById('root')
)
复制代码
keymirror这个库不是必选项,它主要是用来生成 key == value 结构的,好比咱们定义了一堆action类型:
const actionType = {
A: null,
B: null,
C: null
}
// => keymirror
console.log(keymirror(actionType))
// ==>
{
A: 'A',
B: 'B',
C:'C'
}
复制代码
经过keymirror咱们就能够生成key == valuue这样的结构了,有人可能会说这是画蛇添足?其实它能够帮助咱们优化代码,谷歌的Closure Compiler有一种编译模式叫最优处理( Advanced ),会将 Map<K, V> 格式的 K 进行压缩,好比说如下代码:
const Type = {
ASVANCED: null
}
// 通过编译后
const Type = {a: null}
复制代码
这样会使得咱们的代码体积更小,执行的更快。关于actionType的定义方式,接下来我会继续介绍。
reduce-reducers主要是用来拆分reducer用的。想一想若是咱们的项目变得庞大而复杂起来了,要处理的状态很是多,那么咱们都写在一个reducer里是很是不优雅且不利于维护的,以下代码所示:
const reducer = (state, action) => {
switch(action.type){
case actionType.CHECK_FAIL_TODO:
// ...
case actionType.CHECK:
// ...
case actionType.GET_FAIL_TODO:
// ...
case actionType.GET_NUM:
// ...
case actionType.COMPUTE_MONEY:
// ...
//...
default:
return state;
}
}
复制代码
咱们把不一样业务场景的reducer都写在一块儿,后期每每很难管理和维护,咱们指望将不一样业务场景下的reducer进行划分,放到特定的reducer中,以下:
// reducerA.js
const reducerA = (state, action) => {
switch(action.type){
case actionType.CHECK_FAIL_TODO:
// ...
case actionType.CHECK:
//...
default:
return state;
}
}
// reducerB.js
const reducerB = (state, action) => {
switch(action.type){
case actionType.CHECK_FAIL_TODO:
// ...
case actionType.CHECK:
//...
default:
return state;
}
}
// reducerC.js
// ...
// 合并
reducer( reducerA, reducerB, reducerC)
复制代码
每每这种方式是更利于管理和维护的,咱们使用reduce-reducers能够很好的实现这一点,具体用法以下:
import createTodoReducer from './createTodoReducer'
import editTodoReducer from './editTodoReducer'
import doneTodoReducer from './doneTodoReducer'
import delTodoReducer from './delTodoReducer'
import checkFailTodoReducer from './checkFailTodoReducer'
// 将多个reducer合并成一个,并将state合并
import reduceReducers from 'reduce-reducers'
const initialState = {
capacity: 5 * 1024 * 1024, // localStorage总容量,单位bt
curCapacity: 0, // 当前使用容量, 单位bt
hasDoneTodos: [], // 已经完成的todo
hasFailTodos: [], // 已经失败的todo
unDoneTodos: [], // 未完成的todo
// todo建立
ctLoading: false,
ctErrorMes: ''
}
const reducer = reduceReducers(
(state = initialState, action) => createTodoReducer(state, action),
(state = initialState, action) => editTodoReducer(state, action),
(state = initialState, action) => doneTodoReducer(state, action),
(state = initialState, action) => delTodoReducer(state, action),
(state = initialState, action) => checkFailTodoReducer(state, action)
);
export default reducer
复制代码
在了解异步action以前我想先来聊聊redux的中间件机制。相似于koa的中间件,redux一样也支持中间件,而且提供了使用中间件的API,其实原理就是重写action的派发过程,即重写dispatch。关于具体如何写一个中间件,这里不会详细介绍,咱们主要来讲说如何使用redux的中间件机制。
redux提供的applyMiddleware, createStore这两个API,就是咱们使用中间件的关键。咱们在使用中间件时要把中间件传入applyMiddleware函数中,并将applyMiddleware做为createStore的最后一个参数,具体用法以下:
const store = createStore(
reducer,
initial_state,
applyMiddleware(thunk)
);
复制代码
值得注意的是中间件的使用顺序要注意,必定要按照官方的规则和具体业务的顺序来排列中间件。
接下来咱们看看异步action。使用异步action的基本模式以下:
异步action本质上是返回一个函数,在函数里面执行相关操做,可是普通的action返回的是一个对象,那么如何去处理呢?想想咱们上面介绍的redux中间件机制,咱们能够重写dispatch呀,的确,redux-thunk的源码就是对dispatch进行了加工,返回了一个高阶函数,具体源码就不带你们细读了,redux-thunk源码很是简单,十几行代码,很是值得借鉴。下面教你们如何使用redux-thunk:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(thunk)
)
复制代码
这样咱们就能够愉快的编写异步action啦,异步action的伪代码以下:
export default (data, cb) => {
return (dispatch, getState) => {
dispatch(Actions.start());
delay(0.5).then(() => {
dispatch(Actions.ok(data, cb));
}).catch(error => dispatch(Actions.fail(error || '建立失败', cb)))
}
}
复制代码
以前介绍的一大堆都是redux的核心和生态使用方法,也是开发项目的基础,接下啦咱们经过一个实际项目,来带你们完全掌握redux开发。咱们的任务管理平台采用SPA模式开发,脚手架采用笔者本身搭建的webpack,代码能够兼容到ie9. 目录结构以下:
实现后的截图以下:
建立任务:
操做任务:
项目体验地址:XOA任务管理平台
根据以上总结的redux知识点,咱们已经能够开发出如上的任务管理平台了,下一篇文章将具体介绍如何实现这样一个平台以及开发的注意事项和部署相关的知识,感兴趣的朋友能够查看demo尝试研究一下。
若是想获取本次项目完整的源码, 或者想学习更多H5游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入咱们的技术群一块儿学习讨论,共同探索前端的边界。