淺談Flux架構及Redux實踐

Flux概述

Flux是Facebook用來構建用戶端的Web應用程序的體系架構,與其它形式化的框架相比,它更像是一個架構思想,用於管理和控制應用中數據的流向。這裏應用中的數據指包括但不限於來自服務端的數據頁面中view的一些狀態(如一個面板是展開仍是關閉),臨時存儲在本地須要持久化到服務端的數據等。javascript

好了,說了這麼多好像仍是一臉懵逼,不慌,接下來看看展開式。css

clipboard.png

MVC

在講述Flux以前,咱們看看以前傳統的MVC架構以及在前端中的一些問題繼而思考Flux帶來的改變。MVC(Model-View-Controller)最早興起於後端,經過對應用程序複雜度的簡化使程序更加直觀和便於維護。後端程序MVC中View能夠看爲數據的呈現,Model爲數據的模型,Controller做爲程序的流程控制。如今假設有這樣的場景,用戶想查看本身的profile頁面,可能會有這樣的流程:在頁面上點擊profile按鈕,接下來就是一個HTTP請求(/profile?username=jiavan) => Controller接收到這一請求並得到請求的內容username=jiavan而後告知Model須要jiavan的數據 => Model返回了jiavan的數據 => Controller獲得數據返回新的視圖,看下流程:html

clipboard.png

如今前端中又有這樣的場景:切換Menu中的Item,當前選中的Item顏色不一樣於其它顏色而且底部顯示對應Item的內容。通常狀況下咱們會定義一個css class來做爲當前選中Item的樣式。當用戶點擊Item_A爲被點擊的元素新增高亮的class,其它兄弟元素移除該樣式,這裏的事件響應函數就是Controller,咱們會在這裏處理樣式修改邏輯,以及更新Model的數據,而後新的數據及樣式從新渲染界面。這種VC<->M的形式在關係比較簡單的狀況下是比較清晰容易控制的,可是複雜的頁面上這樣的模式可能會變得很是混亂:前端

clipboard.png

之因此變得混亂了,由於不少view都具有修改多個model的能力,這裏的單個修改行爲能夠稱之爲一個Action,一個Action的產生多是用戶行爲,或者一個Ajax請求須要渲染新界面。對比上面後端傳統MVC模式能夠發現:java

  • 後端中Action做爲一個URL請求,前端中多是一個事件;
  • 後端中Action處理被集中在Controller中,而前端中是分散的。

那麼是否是能夠把前端中修改狀態即state的行爲(事件回調/Ajax)所有抽象成一種Action描述,而後交付到一處即Reducers來進行原子化處理,而後Reducer修改整個應用中惟一的一棵state tree即Store,最後經過state->view的機制來從新渲染?react

Flux數據流框架

上面提到的幾個概念已經對Flux有了初步的瞭解,下面進入正題。相信有了解Flux的都應該看過下面這張著名的數據流圖:git

clipboard.png

  1. Action能夠當作是修改Store的行爲抽象;
  2. Dispatcher管理着應用的數據流,能夠看爲Action到Store的分發器;
  3. Store管理着整個應用的狀態和邏輯,相似MVC中的Model。

因此Flux能夠被看做傳統MVC的改進而非顛覆,當我第一次看到Flux的時候實際上是比較懵逼,但看到並使用了Redux後確實有一種很是驚豔的感受。github

Redux

按照Redux官方的描述Redux is a predictable state container for JavaScript apps.,其中predictablestate container體現了它的做用。那麼如何來理解可預測化的呢?這裏會有一些函數式編程方面的思想,在Redux中reducer函數是一個純函數,相同輸入必定會是一致的輸出,因此肯定輸入的state那麼reducer函數輸出的state必定是能夠被預測的,由於它只會進行單純的計算,保證正確的輸出。狀態容器又是什麼?說明Redux有一個專門管理state的地方,就是Store,而且通常狀況下是惟一的,應用中全部state造成的一顆狀態樹就是Store。Redux由Flux演變而來,但受 Elm 的啓發,避開了 Flux 的複雜性,咱們看看其數據流向:編程

clipboard.png

不一樣於Flux架構,Redux中沒有dispatcher這個概念,而且Redux設想你永遠不會變更你的數據,你應該在reducer中返回新的對象來做爲應用的新狀態。可是它們均可以用(state, action) => newState來表述其核心思想,因此Redux能夠被當作是Flux思想的一種實現,可是在細節上會有一些差別。redux

重要概念

  1. 應用中的全部state都以一個object tree的形式存儲在一個單一的store中;
  2. 惟一能改store的方法是觸發action,action是動做行爲的抽象
  3. 爲了描述action如何改變state樹,須要編寫reducer函數。

這裏須要說明一點的是reducer函數,它應當是一個純函數,不該該有反作用,不該有API調用,Date.now()或者隨機獲取等不穩定的操做,應當保證相同的輸入reducer計算的結果應該是一致的輸出,它只會進行單純的計算。編寫reducer函數也是Redux中比較重要的一塊,它的形式以下:

function testReducer(state, action) {
  switch (action.type) {
    case ACTION_TYPE:
      // calc...
      return newState;
    default: return state;
  }
  return newState;
}

state是不可修改的,因此返回的新state應該是基於輸入state副本的修改,而不是直接修改state後的返回。

原則

1. 單一數據源,store

整個應用的state被存放在一棵Object tree中,而且這個Object tree只存在惟一一個store中;

2. state是隻讀的

惟一能改變state的方法是觸發action,action是對已經發生了的事情的抽象描述,簡單的講,它把行爲抽象成了一個對象。

好比,刪除一條記錄的action能夠抽象的理解爲:

{
  type: 'DELETE_ITEM',
  index: 1,
}

3. 使用純函數來實現state歸併操做,reducer

傳入待修改的state和一個告知reducer如何修改state的action,reducer將返回action規則對應下操做後的新的state。

reducer(state, action) => new state

數據流

嚴格的單向數據流是Redux設計的核心
Redux應用數據的生命週期遵循下面4個步驟:

  1. 調用store.dispatch(action), 能夠在任何地方進行;
  2. Redux store調用傳入的reducer函數,而且將當前的state樹與action傳入。reducer是純函數,只用於計算下一個state,它應該是徹底可被預測的,相同的輸入一定會有相同的輸出,不能有反作用的操做,如API的調用或者路由跳轉,這些應該都是在dispatch前產生;
  3. 根reducer將多個子reducer輸出合併成一個單一的state樹;
  4. Redux store保存了根reducer返回的完整的state樹。

新的state樹就是應用的下一個狀態,如今就能夠根據新的state tree來渲染UI。

Redux實踐

咱們經過一個很是簡單的計數器demo來梳理Redux的數據流。

0x00. 建立action

action其實就是一個普通的對象,只是對行爲的抽象描述,這裏咱們能夠把加上一個數描述爲:

{
  type: INCREMENT, //該動做的抽象描述
  number, // 該動做攜帶的數據
}

更多的時候咱們會經過一個action生成函數來獲得一個action:

function incrementCreator(number) {
  return {
    type: INCREMENT,
    number,
  };
}

0x01. 建立reducer函數

reducer做爲整個Redux中action的處理中樞,接收state與action並對此修改數據,返回應用的下一個狀態。

function countReducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return Object.assign({}, {
        counter: state.counter + action.number,
      });
    case DECREMENT:
      return Object.assign({}, {
        counter: state.counter - action.number,
      });
    default: return state;
  }
}

注意:上面咱們已經提到屢次,state是不可修改的,因此經過assign歸併咱們對數據的操做,返回的是state副本修改後的對象,並不是直接修改了輸入的state。

0x02. 建立惟一store

經過Redux中的createStore方法傳入reducer函數來建立整個應用的store。

const store = createStore(countReducer);

0x03. 修改state

經過store的dispatch方法來發起一個action。

store.dispatch(incrementCreator(5));
store.dispatch(decrementCreator(4));

完整demo

import { createStore } from 'redux';

// actions
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// actionCreator,能夠視爲建立action的語法糖
function incrementCreator(number) {
  return {
    type: INCREMENT,
    number,
  };
}

function decrementCreator(number) {
  return {
    type: DECREMENT,
    number,
  };
}

// 初始化state
const initialState = {
  counter: 0,
};

// reducers函數,注意最後必定要return state防止不能匹配到action的時候state丟失
function countReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return Object.assign({}, {
        counter: state.counter + action.number,
      });
    case DECREMENT:
      return Object.assign({}, {
        counter: state.counter - action.number,
      });
    default: return state;
  }
}

// 建立store
const store = createStore(countReducer);

// 訂閱store的修改
const unsubscribe = store.subscribe(function log() {
  console.log(store.getState());
});

// 經過dispatch action來改變state
store.dispatch(incrementCreator(5)); //Object {counter: 5}
store.dispatch(decrementCreator(4)); //Object {counter: 1}

// 取消訂閱
unsubscribe();

參考並推薦閱讀

原文出處 https://github.com/Jiavan/jia... 以爲對你有幫助就給個star吧
相關文章
相關標籤/搜索