介紹 Flux framework 和 Redux

回憶

昨天介紹了 React 的基本概念 state/prop,它們是控制畫面渲染的變數。然而 component 的 state/prop 混合使用會使渲染邏輯變的混亂。

考慮登入畫面: 30天鐵人-Day22-redux1.png

把 username 和 password 分成兩個 component後,關於 hide 值就有二種可能的 component tree 設計。

  1. (component tree 1) 放在 password component 中的 state 30天鐵人-Day22-redux2.png

  2. (component tree 2) 放在最上層 component 中的 state,然後在送到 password component 的 prop 30天鐵人-Day22-redux3.png

假如當使用者按下 SIGN IN 時,希望密碼是隱藏的也就是 hide 需要更動。因為 SIGN IN 是在最上層的 component, 在上面 component tree 1 就會有設定上的困難(因為 state 是在 password component 內部),component tree 2 反而沒有。

試想當你的 component tree 更大時,你應該怎麼管理那些 state 呢? 30天鐵人-Day22-tree.png

目標

今天會介紹 Flux framework 和 Redux。

我覺得不要一開始就想要馬上把 Redux 和 React 串起來,因為你會誤以為 Redux 很複雜。我在本篇未會給出一個直接運作 Redux 的 Node.js 專案,裡面只有一隻程式,執行過程就是 Redux api 的最簡單的範例。

明天才會把 Redux 和 React 串起,你會看到 store, action 被拆到不同的檔案。

Flux framework

Facebook 提出了 Flux framework(或稱架構),來處理 state 和 prop 的混亂。

Flux 的核心思想

Flux 希望資料的流動是單一的 (單向資料流),使用者行為會產生 Action 送入 Dispatcher,Dispatcher 會把 Action 派分到「有註冊要監聽Action的 Store」,然後 Store 依照 Action 的 識別字串 決定如何修改 state,當 Store 修改完 state,會發出 change 訊息。最後, 註冊 change 訊息的 View ,收到來自 Store 的通知,就會執行 setState() 更新畫面(更新內部的component)。

它的流程如下: 30天鐵人-Day22-flux.png

  1. Action:一次修改 state 的行為
  2. Dispatcher:分派 Action 到 Store 的單元
  3. Store:實際存 state 的單元
  4. View:接收 state 並渲染畫面

Flux 的特性

  1. 資料流動的框架:從 Flux 流程上看, Flux 和 React 其實是沒有關連性的,因為它是提出資料流動的框架,跟畫面渲染無關。
  2. 單向資料流:要修改 state 沒有其它方法,只能引起一個 Action
  3. 單元非指向性:每個區塊都是獨立運作的,像是: 一個 Dispatcher 能被多個 Store 註冊、不同 View 可以監聽不同的 Store,任一個單元想接誰就接誰,沒有限定某一個store 只能接某一個 view。

我們注意看到「註冊 Store 的(change)訊息的 View」,它是 state 值流入的第一個 view,被稱成 Controller-View 它負責連結 Store 取出 state 值和引起 setState(),然後送入裡面的 view (即內部 view 的 props)。最後的架構圖如下: 30天鐵人-Day22-fluxfull.png

Flux 的實現套件

以下三個都是 Flux 的實現套件,他們的 API 用法和特性有很大的差異

  1. Facebook Flux:因為保留了Flux「非指向性」的特性,要做比較多 Flux API 串接的操作,用起來不太方更。用法可以看 Flux 架構入門教程。
  2. Alt:隱藏 Dispatcher 的概念,直接把 Action 綁定(bind) 在 Stroe 中,所以 Flux API 串接較方便。用法看官網5分鐘上手就可以了。
  3. Redux:簡化 Flux 完整的概念,API 不多,是我們要用的

Redux 的特性

原本的 Flux Store 和 View 的關係下圖(截錄淺談 React、Flux 與 Redux) IMAGE

然而 Redux 簡化 Flux 的概念,變成 (截錄修改淺談 React、Flux 與 Redux) a1.png

  1. 只有一個 Store:所有 state 放在一個全局的 store 中。這也是最大的差別,不同於 Flux Store 可以有多個 Store,它們各自為戰。
  2. 只有一個 Dispatcher:因為只有一個 Store ,所以全局也只需要有一個 dispatcher,它被隱藏在 store 中。只需 store.dispatch(action) 就可以引發分派 action。
  3. 引入 Reducer 概念:
    • 實際存放 state 的地方
    • store 收到 action 時,reducer 會依照 action type 處理 state 的改變
    • reducer是純函數(pure function):同樣的輸入會有同樣的輸出
      1const initState = {};
      2const reducer = (state = initState, action) => {
      3  const {type, ...actionData} = action;
      4  switch (type) {
      5      default:
      6          return state;
      7  }
      8};
      
    • 只有一個根 reducer 對應 controller-view,其它的 reducer 會被合併,一同管理整個 store。
      1// Reducer configuration
      2const rootReducer = combineReducers({
      3    login: LoginReducer,
      4    account: AccountReducer,
      5});
      

      注意:這裡不是說只能有一個 controller-view,subcomponent 也能是 controller-view

所以 Reducer 簡化 Flux 完整的概念,更容易使用。

體驗 Redux 的運作

Redux 是資料的流動,是改變 state 的框架,最後的 state 給誰用跟它沒有關係,View 是與它無關。因此,React 跟它的運作是沒有關係的。我們可以開一個 Node.js 來運作體驗 Redux。

  1. 開一個空的 Node.js 專案,取名為 hello-redux
  2. 初使化專案 npm init後,安裝 redux npm install redux --save
  3. 建一個 hello-redux.js 檔案
  4. 初使化 Redux,建立 store。我們假設 store 有一個 message state。 建立 store 需要一個 root reducer,而 root reducer 當收到某個 action 後,會看 action type 修改 state
     1const {createStore} = require('redux');
     2
     3// 定義 action type
     4const identityChangeMessage = 'CHANGE_MESSAGE';
     5
     6// 建立一個 Reducer
     7const initState = {
     8  message: 'init message',
     9};
    10const reducer = (state = initState, action) => {
    11  const {type, payload} = action;
    12  switch (type) {
    13      case identityChangeMessage: {
    14        const {message} = payload;
    15        return Object.assign({}, {message});
    16      }
    17      default:
    18          return state;
    19  }
    20};
    21
    22// 建立一個 Store
    23const store = createStore(reducer);
    
  5. 印出初始的 state:store.getState()可以取出目前的 state
    1// 印出初始的 state
    2console.log(store.getState());
    
  6. 建立 action
    1// 建立一個 action
    2const action = {
    3    type: identityChangeMessage,
    4    payload: {
    5        message: 'change',
    6    },
    7}
    
    這裡我們要強調,在 Redux 中:
    • 一個 action 是一個 object,一般會帶有 type 屬性。
    • 一個 action creator 是產生 action 的函數,如:
      1function changeMessage(data) {
      2  return {
      3    type: identityChangeMessage,
      4    payload: {
      5        message: data,
      6    },
      7  }
      8}
      
  7. 分派(dispatch) action
    1store.dispatch(action);
    
    分派 action 出去後,reducer 會收到 action,然後依照 action type (identityChangeMessage) 修改 state
  8. 印出收到 action 後,被 reducer 修改的 state
    1console.log(store.getState());
    

執行 node hello-redux.js 的結果:

1{ message: 'init message' }
2{ message: 'change' }

總結

今天介紹了 Flux 的基本概念和實現它的套件 Redux,也寫了簡單的程式體驗 Redux 的運作。明天就把 Redux 和 React 串接起來。

附錄

完整的 hello-redux.js

 1const {createStore} = require('redux');
 2
 3// 定義 action type
 4const identityChangeMessage = 'CHANGE_MESSAGE';
 5
 6// 建立一個 Reducer
 7const initState = {
 8  message: 'init message',
 9};
10const reducer = (state = initState, action) => {
11  const {type, payload} = action;
12  switch (type) {
13      case identityChangeMessage: {
14        const {message} = payload;
15        return Object.assign({}, {message});
16      }
17      default:
18          return state;
19  }
20};
21
22// 建立一個 Store
23const store = createStore(reducer);
24
25// 印出初始的 state
26console.log(store.getState());
27
28// 建立一個 action
29const action = {
30    type: identityChangeMessage,
31    payload: {
32        message: 'change',
33    },
34}
35
36// 分派(dispatch) action
37store.dispatch(action);
38
39// 印出收到 action 後,被 reducer 修改的 state
40console.log(store.getState());