《完全掌握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數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索