redux 原始碼詳解

鄭大路發表於2019-02-11

redux 單向資料流的由來

  1. Flux將應用分成四個部分;
  • view 檢視層;
  • Action 檢視層發出的訊息;(改變store裡面的資料)
  • Dispatch(派發器)
  • Store (資料層) : 用來存在應用的狀態(資料),一旦發生變動,就要提醒view更新頁面。

redux單向資料流:

具體詳情請見阮一峰Flux架構入門

Action

  • 定義. Action 是把資料從應用(譯者注:這裡之所以不叫 view 是因為這些資料有可能是伺服器響應,使用者輸入或其它非 view 的資料 )傳到 store 的有效載荷。它是 store 資料的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。

  • 狹義的Action

let action = {
    type: 'ACTION_NAME',
    ...
}
複製程式碼

注意: 一般 type 的內容使用 大寫字母+下劃線 的格式.

  • 廣義的Action

    廣義的 action 是指在中介軟體的支援下,dispatch 函式可以呼叫的資料型別,除了普通action之外,常見的有 thunk, promise 等。我們用常用的 thunk來舉個例子

    什麼叫thunk函式? 具體背景見阮一峰es6標準入門一書第17章Thunk函式的含義.在javaScript中函式將多引數函式替換成單引數的版本,且只接受回撥函式作為引數。 例如:

var Thunk = function(fn){
    return function(){
        var args = Array.prototype.slice.call(arguments);
        return function(callback){
            args.push(callback);
            return fn.apply.(this,args);
        }
    }
}

var readFileThunk = Thunk(fs.readFile);

readFileThunk(fileA)(callback);
複製程式碼
Thunk函式版本的action:
複製程式碼
(dispatch, getState) => { 
    //在函式體內可以使用 dispatch 方法來發射其他 action
    //在函式體內可以使用 getState 方法來獲取當前的state
}
複製程式碼

ceateStore

通過該API建立一個store物件,該物件包含四個方法;

  1. getState();獲取store中當前的狀態。
  2. dispatch(action): 分發一個action,並返回這個action,這是唯一能改變store中資料的方式。
  3. subscribe(listener): 註冊一個監聽者,它在store發生變化時被呼叫。
  4. replaceReducer(nextReducer): 更新當前store裡的reducer, 一般只會在開發模式中呼叫該方法。

redux middleware

  • Redux 是一個簡單的同步資料流,當分發一個action時,reducer收到action後,更新state並通知view重新渲染。 當action發出後如果想要執行一些別的操作,該怎樣處理,也就是說action發出後沒有立即執行reducer,將redux變成非同步. 這時就要藉助中介軟體。

  • redux-middleware的資料流動.

  • 中介軟體的由來以及原理. 中介軟體的思想來源於koa. 核心思想:將middleware(函式)進行組合,將當前的middleware執行一遍作為引數傳給下一個middleware去執行。

原理:

app.use((ctx, next) => {
  ctx.name = 'Lucy'
  next()
})

app.use((ctx, next) => {
  ctx.age = 12
  next()
})

app.use((ctx, next) => {
  console.log(`${ctx.name} is ${ctx.age} years old.`) // => Lucy is 12 years old.
  next()
})

// ... 任意呼叫 use 插入中介軟體

app.go({}) // => 啟動執行,最後會呼叫 callback 列印 => { name: 'Lucy', age: 12  }
複製程式碼

ctx 引數就是 app.go 接受的物件。呼叫 app.go 其實會呼叫目標函式 app.callback,但是呼叫 app.callback 之前我們可以先讓引數 ctx 通過一系列的中介軟體,最後才會傳遞給 app.callback。

使用 app.use 插入任意中介軟體,中介軟體是一個函式,可以被傳入一個 ctx 和 next;呼叫 next 的時候會執行下一個中介軟體。如果不呼叫 next 會阻止接下來所有的中介軟體的執行,也不會執行 app.callback。

這裡的app.use()就是一個實現中介軟體。

const app = {
    middleware:[],
    callback(){
        console.log(ctx);
    },
    use(fn){
        this.middleware.push(fn);
    },
    go(){
        const reducer = (next,fn,i)=> { 
           fn(ctx,next)
        }
        this.middleware.reduceRight(reducer,this.callback.bind(this,ctx))();
    }
}
複製程式碼
  • redux的applyMiddleware的原始碼.
    function applyMiddle(){
        (next) => (reducer, initialState) => {
            let store = next(reducer,initialState);
            let dispatch = store.dispatch;
            let chain = [];

            let middlewarAPI = {
                getState:Store.getState,
                dispatch: (action) => { dispatch(action)}
            }
            chain = middlewares.map(middleware => middleware(middlewarAPI));
            dispatch = compose(...chain)(store.dispatch);

            return {
                ...store,
                dispatch
            }
        }
    }
複製程式碼

一般這樣應用middleware

const finalCreateStore = compose(
    applyMiddleware(...middleware)
    //DevTools.instrument()
)(createStore);

const store = finalCreateStore(reducer);
複製程式碼

middleware的一般寫法

const m1 = store => next => action => {
    let result = next(action);

    switch (action.type) {
        case APP_INCREMENT_LOADING:
            globalProgressBar.incrementLoading();

            break;
        case APP_DECREMENT_LOADING:
            globalProgressBar.decrementLoading();

            break;
    }

    return result;
};

export default m1;

複製程式碼

注:這裡的compose函式請參考app.go或者參考上章FP一節; applyMiddle其實用了2箇中介軟體的思想; 原始碼的詳細解釋:

作者個人部落格: http://zhengchengwen.com 歡迎交流

相關文章