Redux

在學習Redux以前,不妨先了解下Flux:javascript

Flux

  • 同MVC同樣,是一種架構思想,但更簡單清晰
  • 解決軟件的結構問題,提供了一套數據流動方案
  • 經過事件和監聽實現數據單向流動,中心化控制

  • View: 視圖層
  • Action(動做):視圖層發出的消息
  • Dispatcher(派發器):用來接收Actions、執行回調函數
  • Store(數據層):用來存放應用的狀態,動態更新Views頁面

一個基本的流程能夠描述爲html

  • Your Views "Dispatch" "Actions"(視圖觸發事件)
  • Your "Store" Responds to Dispatched Actions(store觸發回調)
  • Your Store Emits a "Change" Event(store觸發change事件)
  • Your View Responds to the "Change" Event(視圖接收到事件從新渲染)

與React關係能夠理解爲:前端

Flux:是一個系統架構,用於推動應用中的數據單向流動
React:是一個JavaScript庫,用於構建「可預期」和「聲明式」的可組合式Web用戶界面

問題vue

  • 代碼冗餘
  • dispatcher實例手動建立
  • store既保存狀態數據,又有處理邏輯:直接處理數據
  • 程序可能會涉及多種數據結構,必然致使有多個store
  • 深層次組件通訊問題

參考java

Flux架構入門教程 - ryfengpython

Flux For Stupid Peoplereact

一篇漫畫,圖解Fluxgit

Redux

Redux是基於Flux架構思想的一個庫的實現,JavaScript狀態容器,提供可預測化的狀態管理。es6

設計思想github

  • Web 應用是一個狀態機,視圖與狀態是一一對應的。 
  • 全部的狀態,保存在一個對象裏面。
  • 爲應用程序提供一個可預測的狀態容器

三大原則

  • 單一數據源
  • State 是只讀
  • 使用純函數來執行修改

顯著特色

  • 可預測性(Reducer 是純函數)
  • 可擴展性(middleware)

核心元素

  • store
  • action
  • dispatch
  • reducer
  • subscribe

Redux中的 reducer 就是一個純函數,store.dispatch(_action) 會自動觸發 reducer 方法,更新state。

注意,reducer方法不會改變state,而是返回一個全新的state對象。

關於純函數:一樣的輸入,一定獲得一樣的輸出

  • 不得改寫參數
  • 不能調用系統 I/O 的API
  • 不能調用Date.now()Math.random()等不純的方法,由於每次會獲得不同的結果

適用場景

多交互、多數據源

  • 用戶的使用方式複雜,不一樣身份的用戶有不一樣的使用方式(好比普通用戶和管理員),多個用戶之間能夠協做
  • 與服務器大量交互,或者使用了WebSocket
  • View要從多個來源獲取數據

關於Redux的生態系統,請參見:http://www.redux.org.cn/docs/introduction/Ecosystem.html

store

本質:狀態樹

let { subscribe, dispatch, getState } = createStore(reducer);

關於createStore的基本實現,輔助理解

function createStore(reducer, initialState) {
    //閉包私有變量 
    var currentReducer = reducer;
    var currentState = initialState;
    var listeners = [];

    function getState() {
      return currentState;
    }

    function subscribe(listener) {
      listeners.push(listener);

      return function unsubscribe() {
        var index = listeners.indexOf(listener);
        listeners.splice(index, 1);
      };
    }

    function dispatch(action) {
        currentState = currentReducer(currentState, action);
        listeners.slice().forEach(listener => listener());
        return action;
    }

    //返回一個包含可訪問閉包變量的公有方法
    return {dispatch, subscribe, getState};
}

store能夠看做是對reducer的封裝,經過store.dispatch(action)自動觸發對reducer的調用,而不是直接調用reducer(currentState, action),在必定程度上能夠避免頻繁傳參,以更好地對store進行統一管理。

Action & Action Creator & bindActionCreators

  • action:傳遞操做消息
  • action creator:操做製造機
  • bindActionCreators:對dispatch封裝
var actionCreatorsNew = bindActionCreators(actionCreators, store.dispatch);

借鑑store對reducer的封裝,對store.dispatch做封裝,自動把actionCreators綁定到dispatch,使actionCreators成爲具備操做全局state的函數集合。

其中,actionCreators表示action集合。觸發action,會自動調用dispatch(action),避免直接對dispatch的調用。 

middleware  

異步場景:在異步操做結束後自動執行reducer 

即,如何在操做結束時,自動送出第二個 Action 

擴展點:dispatch()發出時,reducer()處理前

對於中間件,舉例,日誌中間件 redux-logger

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';

const logger = createLogger();
const store = createStore(reducer, initial_state,
  applyMiddleware(logger)
);

other,請注意中間件的引入次序。

給出 applymiddleware 的實現,僅供參考

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

其中,compose 用於組合函數、串聯鏈式執行,順序自右向左,維護擴展方便 

var greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastName
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack', 'smith'))  // ‘HELLO,JACK SMITH’

異步狀況,涉及發出三種不一樣種類的 Action  

  • 操做發起時的 Action
  • 操做成功時的 Action
  • 操做失敗時的 Action

同時,state須要維護、以反映不一樣的操做狀態 

let state = {
  // ... 
  isFetching: true,  // 表示是否在抓取數據
  didInvalidate: true,  // 表示數據是否過期
  lastUpdated: 'xxxxxxx'  // 表示上一次更新時間
};

整個異步操做流程應該是這樣的  

  • 操做開始時,送出一個 Action,觸發 State 更新爲"正在操做"狀態,View 從新渲染
  • 操做結束後,再送出一個 Action,觸發 State 更新爲"操做結束"狀態,View 再一次從新渲染

在Redux中,中間件是純粹的函數,有明確的使用方法而且嚴格的遵循如下格式:

var anyMiddleware = function ({ dispatch, getState }) {
   return function(next) {
       return function (action) {
          // 你的中間件業務相關代碼
       }
   }
}

全部的這些,applyMiddleware會所有替咱們封裝實現。 

異步解決方案能夠引入中間件 redux-thunk 或 redux-promise  

  • redux-thunk:解決stroe.dispatch()的參數只能爲action對象、不能是函數的問題
    • 返回函數的 Action Creator(返回的function在合適的時機dispatch action)
  • redux-promise:容許 Promise 對象做爲stroe.dispatch()的參數,以相似同步的方式來組織代碼
    • 返回 Promise 對象的 Action Creator 
    • 或者,Action 對象的 payload 屬性爲 Promise 對象

可是,二者均是相對原始的解決方案,在action須要組合、取消時操做不易處理。最佳實踐dva推薦 redux-saga,可測試、可mock、聲明式的指令,管理actions,處理異步邏輯,管理全部的業務邏輯。

React-Redux

組件分類

  • UI組件:presentational component,無狀態的純組件,只負責UI視覺
  • 容器組件:container component,有狀態,負責管理數據和邏輯

Redux的重要思想就是:容器組件和展現組件的分離

業務邏輯:針對UI組件

  • 輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數
  • 輸出邏輯:用戶發出的動做如何變爲 Action 對象,從 UI 組件傳出去

在網上看到一個很是不錯的關係圖,分享給你們

connect

將 UI 組件生成容器組件:

const HocView = connect(mapStateToProps, mapDispatchToProps)(myComp)

其中,mapStateToProps負責輸入邏輯、將狀態數據state映射到 UI 組件的參數props,mapDispatchToProps負責輸出邏輯、將用戶對 UI 組件的操做映射成 Action。

mapStateToProps會訂閱Store,每當state更新,就會自動執行,從新計算 UI 組件的參數,從而觸發 UI 組件的從新渲染。

<Provider>

用來實現對store的全局訪問,使容器組件獲取到state:

render(
  <Provider store={store}>
    <Root />
  </Provider>,
  document.getElementById('root')
)

原理是利用React的 context 屬性:  

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}  

子組件經過 this.context.store 獲取。一個簡單的計數器例子,供參見。

Redux擴展: redux-devtools-extension

  • 時間旅行式調試工具,time-travelling tool
  • 熱重加載,hot reloading
  • 基於數據不可變性(immutable)

使用步驟

  • 在Chrome中安裝Redux Devtools擴展
  • npm安裝 redux-devtools-extension
  • import引入

參考

Redux 中文文檔redux-tutorial

Redux教程(1-4) - 阮一峯Redux視頻前30集後30集; 

redux在react中的應用(基礎篇)redux入門教程

看漫畫,學Reduxredux 三重境 - 對 redux 最佳實踐的思考和總結

React 數據流管理架構之 Redux 介紹

redux-saga

前面提到 redux-saga 能夠做爲 Redux 的異步解決方案,簡單學習之,爲後面學習 dva 做個鋪墊。

A Redux middleware for handling side effects (異步任務).

Redux中間件,基於ES6的Generator功能,用於管理應用程序Side Effect的庫,反作用例如

  • 異步獲取數據
  • 訪問瀏覽器緩存

建議先了解下 Generator語法。經過redux-saga中間件將 Saga 與 Redux Store 創建鏈接:

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(mySaga); /// then run the saga

其中,'./sagas' 用於處理全部異步操做邏輯,'./reducers' 用於處理action對stage更新。

 

參考

redux-saga | 中文教程redux-saga-beginner-tutorial

dva

由支付寶前端團隊開發,相關歷史可參見:支付寶前端應用架構的發展和選擇: 從 roof 到 redux 再到 dva

  • 基於現有應用架構 (redux + react-router + redux-saga 等)的一層輕量封裝
  • React + Redux 最佳實踐,簡化使用 redux 和 redux-saga 時繁雜的操做
  • 組件耦合度低
  • 結合了 react 和 vue 二者的優勢,但格式固定、下降了靈活度
  • 除 react 和 react-dom 外,封裝了全部其餘依賴

相關簡介參見:dva - what&why -簡介

注意,dva 是 framework,而 redux 是 library。

最核心功能是提供 app.model 方法,用於把 reducer, initialState, action, saga 封裝到一塊兒

每一個路由對應一個model,這個model掌管該路由的全部狀態(action、state、reducer、sagas),組件想改變狀態只要dispatch type便可。 

參考

React + Redux 最佳實踐 - dva 基於此封裝

相關文章
相關標籤/搜索