iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 22
0
Software Development

用js成為老闆心中的全端工程師系列 第 22 篇

Day 22 - 二周目 - 解開 state/prop 的混亂:Flux 單向資料流暨 Redux 體驗

  • 分享至 

  • xImage
  •  

回憶

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

考慮登入畫面:
https://ithelp.ithome.com.tw/upload/images/20181022/20110371ilLNPMpYsI.png

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

  1. (component tree 1) 放在 password component 中的 state
    https://ithelp.ithome.com.tw/upload/images/20181022/20110371fpRdhbXtDo.png

  2. (component tree 2) 放在最上層 component 中的 state,然後在送到 password component 的 prop
    https://ithelp.ithome.com.tw/upload/images/20181022/20110371ymp1JLFnUq.png

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

試想當你的 component tree 更大時,你應該怎麼管理那些 state 呢?
https://ithelp.ithome.com.tw/upload/images/20181022/20110371yunTnkCrQX.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)。

它的流程如下:
https://ithelp.ithome.com.tw/upload/images/20181022/20110371NOAgPXa8TV.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)。最後的架構圖如下:
https://ithelp.ithome.com.tw/upload/images/20181022/201103719PT5B47VEV.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)
https://ithelp.ithome.com.tw/upload/images/20181022/20110371vvOVMc7UAN.jpg

然而 Redux 簡化 Flux 的概念,變成 (截錄修改淺談 React、Flux 與 Redux)
https://ithelp.ithome.com.tw/upload/images/20181022/20110371Sh1LL9fbCZ.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):同樣的輸入會有同樣的輸出
      const initState = {};
      const reducer = (state = initState, action) => {
        const {type, ...actionData} = action;
        switch (type) {
            default:
                return state;
        }
      };
      
    • 只有一個根 reducer 對應 controller-view,其它的 reducer 會被合併,一同管理整個 store。
      // Reducer configuration
      const rootReducer = combineReducers({
          login: LoginReducer,
          account: AccountReducer,
      });
      

      注意:這裡不是說只能有一個 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
    const {createStore} = require('redux');
    
    // 定義 action type
    const identityChangeMessage = 'CHANGE_MESSAGE';
    
    // 建立一個 Reducer
    const initState = {
      message: 'init message',
    };
    const reducer = (state = initState, action) => {
      const {type, payload} = action;
      switch (type) {
          case identityChangeMessage: {
            const {message} = payload;
            return Object.assign({}, {message});
          }
          default:
              return state;
      }
    };
    
    // 建立一個 Store
    const store = createStore(reducer);
    
  5. 印出初始的 state:store.getState()可以取出目前的 state
    // 印出初始的 state
    console.log(store.getState());
    
  6. 建立 action
    // 建立一個 action
    const action = {
        type: identityChangeMessage,
        payload: {
            message: 'change',
        },
    }
    
    這裡我們要強調,在 Redux 中:
    • 一個 action 是一個 object,一般會帶有 type 屬性。
    • 一個 action creator 是產生 action 的函數,如:
      function changeMessage(data) {
        return {
          type: identityChangeMessage,
          payload: {
              message: data,
          },
        }
      }
      
  7. 分派(dispatch) action
    store.dispatch(action);
    
    分派 action 出去後,reducer 會收到 action,然後依照 action type (identityChangeMessage) 修改 state
  8. 印出收到 action 後,被 reducer 修改的 state
    console.log(store.getState());
    

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

{ message: 'init message' }
{ message: 'change' }

總結

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

附錄

完整的 hello-redux.js

const {createStore} = require('redux');

// 定義 action type
const identityChangeMessage = 'CHANGE_MESSAGE';

// 建立一個 Reducer
const initState = {
  message: 'init message',
};
const reducer = (state = initState, action) => {
  const {type, payload} = action;
  switch (type) {
      case identityChangeMessage: {
        const {message} = payload;
        return Object.assign({}, {message});
      }
      default:
          return state;
  }
};

// 建立一個 Store
const store = createStore(reducer);

// 印出初始的 state
console.log(store.getState());

// 建立一個 action
const action = {
    type: identityChangeMessage,
    payload: {
        message: 'change',
    },
}

// 分派(dispatch) action
store.dispatch(action);

// 印出收到 action 後,被 reducer 修改的 state
console.log(store.getState());


上一篇
Day 21 - 二周目 - React 必備知識和效能議題
下一篇
Day 23 - 二周目 - Redux 串接 React 與 Redux DevTools
系列文
用js成為老闆心中的全端工程師 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
fx777
iT邦新手 5 級 ‧ 2018-10-22 22:57:57

內容好豐富!

PS 標題是不是有 typo XD stete -> state?

謝謝你的提醒
寫到昏頭了/images/emoticon/emoticon06.gif

我要留言

立即登入留言