《完全掌握redux》之开发一个任务管理平台(上)

前言

redux是上手react开发的必经之路,也是目前react项目中使用的最流行状态管理库。虽然咱们不使用redux也能够经过react的state和父子props进行基本的数据通讯和项目开发,可是对于一个大型项目而言,每每考虑的更多的是代码结构和组件之间的通讯,咱们须要一种很优雅且有利于扩展的方式去开发咱们的复杂系统,因此这种状况下使用redux是最佳的选择。javascript

因为以前有朋友但愿快速上手一下redux开发,因此笔者特地开发了一个小项目,但愿经过这个项目可让你们快速掌握redux及其生态的使用方式,以便从此在技术选型上有更多的空间。css

你将收获

  • redux的工做机制和基本概念
  • redux的使用模式
  • redux相关生态的使用(react-redux, keymirror, reduce-reducers)
  • 异步action解决方案redux-thunk
  • 项目技术选型和架构
  • 基于react实现一个可用的任务管理平台
  • 如何实现本身的js工具库

正文

1. redux的工做机制和基本概念

以上是笔者画的一个草图,描述了redux的数据流起色制。首先是用户触发action(在代码层面只有dispatch才能触发action),这时store会自动调用reducer函数并传入上一个状态的state和action,reducer函数会返回一个新的state,这个时候store会监听state的变化并调用监听函数,此时咱们的react组件就会从新渲染并生成新的view。

redux的设计思想核心就是把web应用看成一个状态机,视图和状态一一对应,全部的状态都保存在一个对象里前端

由上图能够看出redux几个核心api就是vue

  • store 保存数据的容器
  • state 某个时刻store的快照
  • action 标识当前要执行的动做。 action是改变 State 的惟一方式
  • dispatch 执行action的惟一方式
  • reducer 计算并生成一个新state的方式

咱们只要理清它们的关系和工做机制,redux也就能轻松使用了。java

2. redux的使用模式

redux的基本工做流程熟悉以后,咱们来看看如何将redux运用在项目中。如下是使用redux的基本步骤,你们能够参考一下:node

  1. 定义初始化的state
  2. 定义action
  3. 编写reducer函数
  4. 使用dispatch触发action

基本代码以下: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

3. redux相关生态的使用(react-redux, keymirror, reduce-reducers)

3.1 react-redux

react-redux的核心思想是将全部组件分红渲染组件(纯组件)和容器组件(负责处理业务逻辑和状态),渲染组件只负责展现,没有状态,容器组件负责处理各类状态和逻辑。因此用户只须要提供渲染组件来呈现视图,容器组件会由react-redux自动生成。因此整个过程看上去像这样: web

咱们来看看如何使用react-redux。首先它提供了connect方法用于从 UI 组件生成容器组件,并将UI组件和容器组件链接在一块儿,具体用法以下:

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')
)
复制代码

3.2 keymirror

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的定义方式,接下来我会继续介绍。

3.3 reduce-reducers

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
复制代码

4. 异步action解决方案redux-thunk

在了解异步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,一共有两种方式实现如上步骤:

  1. 在业务代码中的请求回调中触发同步的action
  2. 使用异步action 对于简单应用咱们彻底能够采用第一种方式来作,也就不须要异步action了,可是每次异步请求时都手动调用两个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)))
	}
}
复制代码

5. 项目技术选型和架构

以前介绍的一大堆都是redux的核心和生态使用方法,也是开发项目的基础,接下啦咱们经过一个实际项目,来带你们完全掌握redux开发。咱们的任务管理平台采用SPA模式开发,脚手架采用笔者本身搭建的webpack,代码能够兼容到ie9. 目录结构以下:

你们在实际项目开发中也能够按照本身团队的风格,根据项目体量来量身定制本身的项目结构。store就是存放咱们redux工做流的地方,也是整个状态的管理中心。 UI库笔者采用本身开发的XUI组件库,目前已迭代了5个版本,后续会继续优化。具体参考地址以下: xui——基于react的轻量级UI组件库

6. 基于react实现一个可用的任务管理平台

实现后的截图以下:

咱们经过这个任务管理平台,能够实现:

  • 建立任务
  • 编辑修改任务
  • 删除任务
  • 任务到期自动提醒⏰
  • 任务效率分析
  • 任务记录
  • 空间占用分析

建立任务:

操做任务:

任务效率分析,任务记录,空间占用分析

项目体验地址:XOA任务管理平台

根据以上总结的redux知识点,咱们已经能够开发出如上的任务管理平台了,下一篇文章将具体介绍如何实现这样一个平台以及开发的注意事项和部署相关的知识,感兴趣的朋友能够查看demo尝试研究一下。

最后

若是想获取本次项目完整的源码, 或者想学习更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入咱们的技术群一块儿学习讨论,共同探索前端的边界。

更多推荐

相关文章
相关标签/搜索