Redux技術架構簡介(一)

brantni發表於2018-05-09

Redux是一個程式架構,源於Flux(Facebook提出的一種架構),然而,它不僅可以應用於React,還可以應用於其他任何框架中。值得一提的是,Redux的原始碼很少,但是他的邏輯拆分和函數語言程式設計的設計思想是非常值得學習的。

1. 解決的問題

當一個JavaScript單頁應用變得越來越複雜時,我們要處理的資料(model)也變得越來越龐大,隨之而來的是每個資料的狀態(state)會變得難以維護。當一個model改變時,我們可能要手動處理由此引發的其他model的變化,更糟糕的是,其他model變化可能又會引起另一些model的變化,這樣產生連鎖反應。最後我們很容易就會不記得model在什麼時候以及如何發生改變。這正是Redux可以解決的最大痛點之一—以統一的方式管理資料狀態。

統一的資料狀態管理體現為以下2個方面:

  • 元件間的資料通訊
  • UI和資料處理邏輯分離

2. 三大原則

  • 單一資料來源
    整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。
  • State是隻讀的
    唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。
  • Reducer必須是純函式的

3. 基本概念

(1) Action

Action 是把資料從應用傳到 store 的有效載荷。它是 store 資料的唯一來源。直白的說,action就是一種訊息型別,他告訴Redux是時候該做什麼了,並帶著相應的資料傳到Redux內部(即接下來介紹的Reducer)。
Action就是一個簡單的物件,其中必須要有一個type屬性,用來標誌動作型別(reducer以此判斷要執行的邏輯),其他屬性使用者可以自定義,但要儘量簡單。如:
{type: SET_ALL,index: 5}

(2) Action Creator

Action Creator是一個用來自動生成Action的函式,這也是Redux函數語言程式設計的一種體現。通常,我們會按照業務邏輯或元件把相關的Action Creator放在一個檔案中,形式如下:

export const SHOW_SLIDER = 'SHOW_SLIDER';
export const RIGHT_SLIDER = 'RIGHT_SLIDER';
export const showSliderAction = (index) => {
   return {
       type: SHOW_SLIDER,
       index
   };
}
export const rightSliderAction = (length) => {
   return {
       type: RIGHT_SLIDER,
       length
   };
} 
複製程式碼

其中showSliderAction和rightSliderAction就是Action Creator。
由於Action Creator的形式大體相同,我們還可以建立一個用來生成Action Creator的函式以進一步簡化。

export const makeCreateAction = (type, ...keys) => {
    return (...data) => {
        let action = {type};
        keys.forEach((v,i) => action[v] = data[i]);
        return action;
    }
}
export const showSliderAction = makeCreateAction(SHOW_SLIDER, index);
export const rightSliderAction = makeCreateAction(RIGHT_SLIDER, length);
複製程式碼

(3) Reducer

Reducer 指定了應用狀態的變化如何響應 actions 併傳送到 store 的,action只是告訴Redux該幹什麼了,並沒有告訴他怎麼幹,而reducer就是根據action處理state如何改變的邏輯。
Reducer必須是一個純函式(原因稍後解釋),他根據action處理state的更新,如果沒有更新或遇到未知action,則返回舊state;否則返回一個新state物件。注意:不能修改舊state,必須先拷貝一份state,再進行修改,也可以使用Object.assign函式生成新的state。另外,state引數需先進行初始化。例項程式碼如下:

//初始狀態
let initialState = {hiddenClass: 'g-hidden',currentIndex:0};
let sliderReducer = function (state = initialState, action) {
    switch(action.type){
        case sliderAction.SHOW_SLIDER:
            return {hiddenClass: '',currentIndex:action.index};
        case sliderAction.RIGHT_SLIDER:
            if(state.currentIndex == action.length-1){
                return Object.assign({}, state, {currentIndex:0});
            }else{
                return Object.assign({}, state, {currentIndex:Number.parseInt(state.currentIndex)+1});
            }
        default:
            return state;
    }
}

export default sliderReducer;
複製程式碼
  • 使用純函式的原因:
    首先,純函式的特點是:

    • 函式的返回結果只依賴於它的引數。
    • 函式執行過程裡面沒有副作用。(不會對外界產生影響)

    如果不使用純函式,即直接更改state值會怎麼樣呢?

 … …      
    case sliderAction.RIGHT_SLIDER:
        if(state.currentIndex == action.length-1){
    		state.currentIndex = 0;
            return state;
        }
    … …
複製程式碼

之後會發現,無論state如何變化,UI都不會更新。以下是Redux的部分原始碼:

Redux技術架構簡介(一)
通過檢視Redux原始碼得知,新舊state的比較只是對引用地址的比較,如果reducer只是返回舊state(即previousStateForKey)的更新,新state(nextStateForKey)實際上和舊state引用的都是同一塊記憶體地址,所以無論如何更改,新舊state始終保持相同。這就是為什麼reducer必須是純函式的原因。

  • Reducer拆分與合併:
    Reducer 函式負責生成 State。由於整個應用只有一個 State 物件,包含所有資料,對於大型應用來說,這個 State 必然十分龐大,導致 Reducer 函式也十分龐大。所以需要先對reducer進行拆分,拆分的原則可以按業務邏輯進行劃分,如果是react的話,可以直接和react的元件相對應進行劃分。
    劃分好之後,可以用Redux提供的combineReducers方法進行合併,十分方便。
    import { combineReducers } from 'redux';
    import photomainReducer from './photomainReducer';
    import sortReducer from './sortReducer';
    import sliderReducer from './photoSliderReducer';
    
    export default combineReducers({
        photomainReducer,
        sortReducer,
        sliderReducer
    });
    複製程式碼

    (3) Store

    Store 是把Action、Reducer聯絡到一起的物件。Store 有以下職責:

    1.維持應用的 state;
    2.提供 getState() 方法獲取 state;
    3.提供 dispatch(action) 方法更新 state;
    4.通過 subscribe(listener) 註冊監聽器;
    5.通過 subscribe(listener) 返回的函式登出監聽器。

    建立store

    Redux應用應該只有一個store,他提供了建立store的API—createStore(reducer, initState)。第一個引數為一個reducer,可以接受通過combineReducers合併後的reducer,第二個是可選引數,意思是可以設定應用的初始state。
    const store  = createStore(
            indexPhotomainReducer,
        );
    export default store;
    複製程式碼

    State

    應用的state也應該只有一個,裡面包含了所有的應用資料。當需要獲取state的時候,需要使用store.getState()方法。

    store.dispatch(action)

    UI更新state 的唯一途徑,通過dispatch方法發起action,喚起對應的reducer更新state。

    store. subscribe(listener)

    通過此方法可以設定監聽函式,一旦state發生變化,就會立即呼叫監聽函式(listener)。同時,subscribe的返回值(實際上是一個unsubscribe函式)可以解除監聽。如:
    // 每次 state 更新時,列印日誌
    // 注意 subscribe() 返回一個函式用來登出監聽器
    const unsubscribe = store.subscribe(() =>
       console.log(store.getState())
    )
    
    // 停止監聽 state 更新
    unsubscribe();
    複製程式碼

4. 資料流

Redux技術架構簡介(一)

  • 首先,使用者發出 Action(如click事件)。
store.dispatch(SHOW_SLIDER)
複製程式碼
  • Store 自動呼叫 Reducer,並且傳入兩個引數:當前 State 和收到的 Action。 Reducer根據Action的type呼叫相應的分支, 返回新的 State 。
let initialState = {hiddenClass: 'g-hidden',currentIndex:0};

let sliderReducer = function (state = initialState, action) {
   switch(action.type){
       case sliderAction.SHOW_SLIDER:
           return {hiddenClass: '',currentIndex:action.index};
       default:
           return state;
   }
	}
複製程式碼
  • State 一旦有變化,Store 就會呼叫監聽函式。
store.subscribe(listener);
複製程式碼
  • listener可以通過store.getState()得到當前狀態。如果使用的是 React,這時可以觸發重新渲染 View。
function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}
複製程式碼

以上為Redux的資料流動過程。

本篇到此告一段落,下一篇介紹Redux的非同步實現

參考

Redux 中文文件
Redux 入門教程-阮一峰

相關文章