一起學(xué)習(xí)造輪子(二):從零開始寫一個Redux,輪子redux

本文是一起學(xué)習(xí)造輪子系列的第二篇,本篇我們將從零開始寫一個小巧完整的Redux,本系列文章將會選取一些前端比較經(jīng)典的輪子進行源碼分析,并且從零開始逐步實現(xiàn),本系列將會學(xué)習(xí)Promises/A+,Redux,react-redux,vue,dom-diff,webpack,babel,kao,express,async/await,jquery,Lodash,requirejs,lib-flexible等前端經(jīng)典輪子的實現(xiàn)方式,每一章源碼都托管在github上,歡迎關(guān)注~
相關(guān)系列文章:
一起學(xué)習(xí)造輪子(一):從零開始寫一個符合Promises/A+規(guī)范的promise
一起學(xué)習(xí)造輪子(二):從零開始寫一個Redux
一起學(xué)習(xí)造輪子(三):從零開始寫一個React-Redux
本系列g(shù)ithub倉庫:
一起學(xué)習(xí)造輪子系列g(shù)ithub(歡迎star~)

前言

Redux是JavaScript狀態(tài)容器,提供可預(yù)測化的狀態(tài)管理。愛掏網(wǎng) - it200.com本文將會詳細介紹Redux五個核心方法 createStore,applyMiddleware,bindActionCreators,combineReducers,compose的實現(xiàn)原理,最后將自己封裝一個小巧完整的redux庫,隨后會介紹一下經(jīng)常與Redux一起結(jié)合使用的Redux常用中間件redux-logger,redux-thunk,redux-promise等中間件的實現(xiàn)原理。愛掏網(wǎng) - it200.com

本文對于Redux是什么及Redux幾個核心方法如何使用只會做簡單介紹,如果還沒用過Redux建議先學(xué)習(xí)基礎(chǔ)知識。愛掏網(wǎng) - it200.com

推薦文章:
Redux 入門教程(一):基本用法
Redux 入門教程(二):中間件與異步操作
Redux 入門教程(三):React-Redux 的用法

本文所有代碼在github建有代碼倉庫,可以點此查看本文代碼,也歡迎大家star~

開始

首先,我們先來看一種使用Redux的基礎(chǔ)場景:


function reducer(state, action) {}

const store = createStore(reducer) //用reducer生成了store

store.subscribe(() => renderApp(store.getState())) //注冊state變化的回調(diào)

renderApp(store.getState()) //初始化頁面

store.dispatch(xxxaction) //發(fā)出action



上面代碼是一個用到Redux的基礎(chǔ)場景,首先定義了一個reducer,然后用這個reducer生成了store,在store上注冊當(dāng)state發(fā)生變化后要執(zhí)行的回調(diào)函數(shù),然后使用初始state先渲染一下頁面,當(dāng)頁面有操作時,store.dispatch發(fā)出一個action,action和舊的state經(jīng)過reducer計算生成新的state,此時state變化,觸發(fā)回調(diào)函數(shù)使用新的state重新渲染頁面,這個簡單的場景囊括了整個redux工作流, 如圖所示:
2f3048a079941805c33f64c6913ccb91eee6ee2b
這個場景主要用到Redux里面的createStore方法,這是Redux里最核心的方法,下面我們簡單實現(xiàn)一下這個方法。愛掏網(wǎng) - it200.com


function createStore(reducer) {
    let state = null //用來存儲全局狀態(tài)
    let listeners = [] //用來存儲狀態(tài)發(fā)生變化的回調(diào)函數(shù)數(shù)組

    const subscribe = (listener) => { //用來注冊回調(diào)函數(shù)
        listeners.push(listener)
    }
    const getState = () => state //用來獲取最新的全局狀態(tài)
    const dispatch = (action) => { //用來接收一個action,并利用reducer,根據(jù)舊的state和action計算出最新的state,然后遍歷回調(diào)函數(shù)數(shù)組,執(zhí)行回調(diào).
        state = reducer(state, action) //生成新state
        listeners.forEach((listener) => listener()) //執(zhí)行回調(diào)
    }

    dispatch({}) //初始化全局狀態(tài)
    return { getState, dispatch, subscribe } //返回store對象,對象上有三個方法供外部使用
}

其實實現(xiàn)這個方法并不復(fù)雜

  1. 首先,定義2個變量,一個是state,一個是listeners,state用來存放全局狀態(tài),listeners用來存儲狀態(tài)發(fā)生變化的回調(diào)函數(shù)數(shù)組。愛掏網(wǎng) - it200.com
  2. 然后定義三個方法subscribe,getState,dispatch。愛掏網(wǎng) - it200.comsubscribe用于注冊回調(diào)函數(shù),getState用來獲取最新的state狀態(tài),dispatch用來接收一個action,并利用reducer,根據(jù)舊的state和action計算出最新的state,然后遍歷回調(diào)函數(shù)數(shù)組,執(zhí)行回調(diào)。愛掏網(wǎng) - it200.com
  3. 當(dāng)調(diào)用createStore時,會先執(zhí)行dispatch({})利用reducer生成一個初始state,然后返回一個store對象,對象上掛載著getState, dispatch, subscribe這三個方法供外部調(diào)用

經(jīng)過以上三步,我們便實現(xiàn)了一個簡單的createStore方法。愛掏網(wǎng) - it200.com

我們在開發(fā)稍微大一些的項目時reducer一般有多個,我們會一般會建立一個reducers文件夾,里面存儲項目中用到的所有reducer,然后使用一個combineReducers方法將所有reducer合并成一個傳給createStore方法。愛掏網(wǎng) - it200.com



import userInfoReducer from './userinfo.js'
import bannerDataReducer from './banner.js'
import recordReducer from './record.js'
import clientInfoReducer from './clicentInfo.js'

const rootReducer = combineReducers({
    userInfoReducer,
    bannerDataReducer,
    recordReducer,
    clientInfoReducer
})

const store = createStore(rootReducer)


接下來,我們就一起來實現(xiàn)combineReducers這個方法:


const combineReducers = reducers => (state = {}, action) => {
    let currentState = {};
    for (let key in reducers) {
        currentState[key] = reducers[key](state[key], action);
    }
    return currentState;
};

首先combineReducers這個函數(shù)接收一個reducer集合,返回一個合并后的reducer函數(shù),所以返回的函數(shù)傳參仍然和平常的reducer一樣,接收state和action,返回新的state。愛掏網(wǎng) - it200.com 然后聲明一個currentState對象,用來存儲全局狀態(tài),接著遍歷reducers數(shù)組,使用reducer函數(shù)生成對應(yīng)的state對象掛載到currentState上。愛掏網(wǎng) - it200.com 比如說reducers里傳入了2個reducer{userInfoReducer,bannerDataReducer},userInfoReducer里state本來是這樣:{userId:1,name:"張三"},而bannerDataReducer里的state本來是{pictureId:1,pictureUrl:"http://abc.com/1.jpg"} 合并以后的currentState變?yōu)?
{
    userInfoReducer: {
        userId: 1,
        name: "張三"
    },
    bannerDataReducer: {
        pictureId: 1,
        pictureUrl: "http://abc.com/1.jpg"
    }
}

到此我們實現(xiàn)了第二個方法combineReducers。愛掏網(wǎng) - it200.com

接下來介紹bindActionCreators這個方法,這是redux提供的一個輔助方法,能夠讓我們以方法的形式來調(diào)用action。愛掏網(wǎng) - it200.com同時,自動dispatch對應(yīng)的action。愛掏網(wǎng) - it200.com它接收2個參數(shù),第一個參數(shù)是接收一個action creator,第二個參數(shù)接收一個 dispatch 函數(shù),由 Store 實例提供。愛掏網(wǎng) - it200.com

比如說我們有一個TodoActionCreators


export function addTodo(text) {
    return {
      type: 'ADD_TODO',
      text
    };
}
export function removeTodo(id) {
   return {
     type: 'REMOVE_TODO',
     id
   };
}

我們之前需要這樣使用:



import * as TodoActionCreators from './TodoActionCreators';

let addReadAction = TodoActionCreators.addTodo('看書');
dispatch(addReadAction);

let addEatAction = TodoActionCreators.addTodo('吃飯');
dispatch(addEatAction);

let removeEatAction = TodoActionCreators.removeTodo('看書');
dispatch(removeEatAction);

現(xiàn)在只需要這樣:


import * as TodoActionCreators from './TodoActionCreators';
let TodoAction = bindActionCreators(TodoActionCreators, dispatch);

TodoAction.addTodo('看書')
TodoAction.addTodo('吃飯')
TodoAction.removeTodo('看書')


好了,說完了如何使用,我們來實現(xiàn)一下這個方法



function bindActionCreator(actions, dispatch) {
    let newActions = {};
    for (let key in actions) {
        newActions[key] = () => dispatch(actions[key].apply(null, arguments));
    }
    return newActions;
}

方法實現(xiàn)也不難,就是遍歷ActionCreators里面的所有action,每個都使用一個函數(shù)進行包裹dispatch行為并將這些函數(shù)掛載到一個對象上對外暴露,當(dāng)我們在外部的調(diào)用這個函數(shù)的時候,就會自動的dispatch對應(yīng)的action,這個方法的實現(xiàn)其實也是利用了閉包的特性。愛掏網(wǎng) - it200.com

這個方法在使用react-redux里面經(jīng)常見到,等講react-redux實現(xiàn)原理時會再說一下。愛掏網(wǎng) - it200.com

最后,還剩兩個方法,一個是compose,一個是applyMiddleware,這兩個都是使用redux中間件時要用到的方法,先來說說compose這個方法,這是一個redux里的輔助方法,其作用是把一系列的函數(shù),組裝生成一個新的函數(shù),并且從后到前依次執(zhí)行,后面函數(shù)的執(zhí)行結(jié)果作為前一個函數(shù)執(zhí)行的參數(shù)。愛掏網(wǎng) - it200.com

比如說我們有這樣幾個函數(shù):


function add1(str) {
    return str + 1
}

function add2(str) {
    return str + 2
}

function add3(str) {
    return str + 3
}

我們想依次執(zhí)行函數(shù),并把執(zhí)行結(jié)果傳到下一層就要像下面一樣一層套一層的去寫:
let newstr = add3(add2(add1("abc"))) //"abc123"
這只是3個,如果數(shù)量多了或者數(shù)量不固定處理起來就很麻煩,但是我們用compose寫起來就很優(yōu)雅:

let newaddfun = compose(add3, add2, add1);
let newstr = newaddfun("abc") //"abc123"

那compose內(nèi)部是如何實現(xiàn)的呢?



function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

其實核心代碼就一句,這句代碼使用了reduce方法巧妙地將一系列函數(shù)轉(zhuǎn)為了add3(add2(add1(...args)))這種形式,我們使用上面的例子一步一步地拆分看一下,當(dāng)調(diào)用compose(add3, add2, add1),funcs是add3, add2, add1,第一次進入時a是add3,b是add2,展開就是這樣子:(add3, add2)=>(...args)=>add3(add2(...args)),傳入了add3, add2,返回一個這樣的函數(shù)(...args)=>add3(add2(...args)),然后reduce繼續(xù)進行,第二次進入時a是上一步返回的函數(shù)(...args)=>add3(add2(...args)),b是add1,于是執(zhí)行到a(b(...args)))時,b(...args)作為a函數(shù)的參數(shù)傳入,變成了這種形式:(...args)=>add3(add2(add1(...args))),是不是很巧妙。愛掏網(wǎng) - it200.com

最后我們來看最后一個方法applyMiddleware,我們在redux項目中,使用中間件時一般這樣寫:



import thunk from 'redux-thunk'
import logger from 'redux-logger'
const middleware = [thunk, logger]
const store = createStore(rootReducer, applyMiddleware(...middleware))

上面我們用到了thunk和logger這兩個中間件,在createStore創(chuàng)建倉庫時傳入一個新的參數(shù)applyMiddleware(...middleware),在此告訴redux我們要使用的中間件,所以我們要先改造一下createStore方法,讓其支持中間件參數(shù)的傳入。愛掏網(wǎng) - it200.com

function createStore(reducer, enhancer) {
    //如果傳入了中間件函數(shù),使用中間件增強createStore方法
    if (typeof enhancer === 'function') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}

然后接下來以redux-logger中間件為例來分析一下redux中間件的實現(xiàn)方式。愛掏網(wǎng) - it200.com 首先我們可以先思考一下,如果我們不用logger中間件,想實現(xiàn)logger的功能該怎樣做呢?


let store = createStore(reducer);
let dispatch = store.dispatch;
store.dispatch = function (action) {
  console.log(store.getState());
  dispatch(action);
  console.log(store.getState())
};

我們可以在原始dispatch方法外面包裝一層函數(shù),讓發(fā)起真正的dispatch之前和之后都打印一下日志,調(diào)用時調(diào)用包裝后的這個dispatch函數(shù),其實redux中間件原理的思路就是這樣的:將store的dispatch進行替換,換成一個功能增強了但是仍然具有dispach功能的新函數(shù)。愛掏網(wǎng) - it200.com

那applyMiddleware方法里是如何改造dispatch來增強功能的呢?首先我們來看個簡單版本,假如我們只有一個中間件,如何實現(xiàn)applyMiddleware方法呢?


function applyMiddleware(middleware) {
    return function a1(createStore) {
        return function a2(reducer) {
            //取出原始dispatch方法
            const store = createStore(reducer)
            let dispatch = store.dispatch

            //包裝dispatch
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
            }
            let mid = middleware(middlewareAPI)
            dispatch = mid(store.dispatch)
            
            //使用包裝后的dispatch覆蓋store.dispatch返回新的store對象
            return {
                ...store,
                dispatch
            }
        }
    }
}
//中間件
let logger = function({ dispatch, getState }) {
        return function l1(next) {
            return function l2(action) {
                console.log(getState());
                next(action)
                console.log(getState())
            }
        }
    }
//reducer函數(shù)
function reducer(state, action) {
    if (!state) state = {
        count: 0
    }
    console.log(action)
    switch (action.type) {
        case 'add':
            let obj = {...state,
                count: ++state.count
            }
            return obj;
        case 'sub':
            return {...state,
                count: --state.count
            }
        default:
            return state
    }
}

const store = createStore(reducer, applyMiddleware(logger))

首先我們定義了的applyMiddleware方法,它接收一個中間件作為參數(shù)。愛掏網(wǎng) - it200.com然后定義了一個logger中間件函數(shù),它接收dispatch和getState方法以供內(nèi)部使用。愛掏網(wǎng) - it200.com這兩個函數(shù)Redux源碼里都是使用高階函數(shù)實現(xiàn)的,在這里與源碼保持一致也使用高階函數(shù)實現(xiàn),但是為了方便理解,使用具名的function函數(shù)代替匿名箭頭函數(shù)可以看得更清晰。愛掏網(wǎng) - it200.com

當(dāng)我們執(zhí)行const store = createStore(reducer,applyMiddleware(logger))時,首先applyMiddleware(logger)執(zhí)行,將logger存在閉包里,然后返回了一個接收createStore方法的函數(shù)a1,將a1這個函數(shù)作為第二個參數(shù)傳入createStore方法,因為傳入了第二個參數(shù),所以createstore里面其實會執(zhí)行這一段代碼:



if (typeof enhancer === 'function') {
    return enhancer(createStore)(reducer)
}

當(dāng)執(zhí)行return enhancer(createStore)(reducer),其實執(zhí)行的是a1(createStore)(reducer),當(dāng)執(zhí)行a1(createStore)時返回a2,最后return的是a2(reducer)的執(zhí)行結(jié)果。愛掏網(wǎng) - it200.com

  1. 然后,我們看看a2內(nèi)部都做了些什么,我給這個函數(shù)定義了三個階段,首先為取出原始dispatch階段,這一階段執(zhí)行createStore(reducer)方法,并拿出原始的dispatch方法。愛掏網(wǎng) - it200.com

  2. 接著,我們到了第二個階段包裝原始dispatch,首先我們定義了middlewareAPI用來給中間件函數(shù)使用,這里的getState直接使用了store.getState,而dispatch使用函數(shù)包了一層,(action)=>dispatch(action),為什么呢,因為我們最終要給中間件使用的dispatch方法,一定是經(jīng)過各種中間件包裝后的dispatch方法,而不是原方法,所以我們這里將dispatch方法設(shè)置為一個變量。愛掏網(wǎng) - it200.com然后將middlewareAPI傳入middleware執(zhí)行,返回一個函數(shù)mid(也就是logger里面的l1),這個函數(shù)接收一個next方法作為參數(shù),然后當(dāng)我們執(zhí)行dispatch = mid(store.dispatch)時,將store.dispatch作為next方法傳入,并把返回的函數(shù)l2作為新的dispatch,我們可以看到新的dispatch方法其實里面做了和我們上面自己直接改造store.dispatch做了同樣的事情:



function l2(action) {
    console.log(getState());
    next(action)
    console.log(getState())
}

都是接收一個action,先打印日志,然后執(zhí)行原始的dispatch方法去發(fā)一個action,然后再打印日志。愛掏網(wǎng) - it200.com

  1. 最后到了第三個階段:使用包裝后的dispatch覆蓋store.dispatch方法后返回新的store對象。愛掏網(wǎng) - it200.com

  2. 到此,當(dāng)我們在外面執(zhí)行store.dispatch({type:add})時,實際上執(zhí)行的是包裝后的dispatch方法,所以logger中間件就生效了,如圖所示真正發(fā)起dispatch的前后都打印出了最新狀態(tài):


7a8674038f0f5f4dbba233971de17ba414b3520a
現(xiàn)在我們在上一版applyMiddleware的基礎(chǔ)上再改造,使其支持多個中間件:


import compose from './compose';

function applyMiddleware(...middlewares) {
    return function a1(createStore) {
        return function a2(reducer) {
            const store = createStore(reducer)
            let dispatch = store.dispatch
            let chain = []

            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
            }
            chain = middlewares.map(middleware => middleware(middlewareAPI))
            dispatch = compose(...chain)(store.dispatch)

            return {
                ...store,
                dispatch
            }
        }
    }
}

let loggerone = function({ dispatch, getState }) {
    return function loggerOneOut(next) {
        return function loggerOneIn(action) {
            console.log("loggerone:", getState());
            next(action)
            console.log("loggerone:", getState())
        }

    }
}
let loggertwo = function({ dispatch, getState }) {
    return function loggerTwoOut(next) {
        return function loggerTwoIn(action) {
            console.log("loggertwo:", getState());
            next(action)
            console.log("loggertwo:", getState())
        }
    }
}
const store = createStore(reducer, applyMiddleware([loggertwo, loggerone]))

首先當(dāng)調(diào)用applyMiddleware方法時,由傳入一個中間件變?yōu)閭魅胍粋€中間件數(shù)組。愛掏網(wǎng) - it200.com

然后我們在applyMiddleware方法中維護一個chain數(shù)組,這個數(shù)組用于存儲中間件鏈。愛掏網(wǎng) - it200.com

當(dāng)執(zhí)行到 chain = middlewares.map(middleware => middleware(middlewareAPI))時,chain里面存放的是[loggerTwoOut,loggerOneOut]。愛掏網(wǎng) - it200.com

然后下一步我們改造dispatch時用到了我們之前講過的compose方法,dispatch=compose(...chain)(store.dispatch)其實相當(dāng)于是執(zhí)行了dispatch =loggerTwoOut(loggerOneOut(store.dispatch)),然后這一句loggerTwoOut(loggerOneOut(store.dispatch))再次拆開看一下是如何執(zhí)行的,當(dāng)執(zhí)行loggerOneOut(store.dispatch),返回loggerOneIn函數(shù),并將store.dispatch方法作為loggerOneIn里面的next方法。愛掏網(wǎng) - it200.com現(xiàn)在函數(shù)變成了這樣:loggerTwoOut(loggerOneIn),當(dāng)執(zhí)行這一句時,返回loggerTwoIn函數(shù),并將loggerOneIn作為loggerTwoIn方法里的next方法。愛掏網(wǎng) - it200.com最后給dispatch賦值:dispatch =loggerTwoIn。愛掏網(wǎng) - it200.com

在外部我們調(diào)用store.dispatch({type:add})時,實際執(zhí)行的是loggerTwoIn({type:add}),所以會先執(zhí)行 console.log("loggertwo:", getState()),然后執(zhí)行next(action)時執(zhí)行的其實是loggerOneIn(action),進入到loggerOneIn內(nèi)部,所以會執(zhí)行console.log("loggerone:",getState());然后執(zhí)行next(action),這里的其實執(zhí)行的是原始的store.dispatch方法,所以會真正的把action提交,提交完后繼續(xù)執(zhí)行,執(zhí)行console.log("loggerone:",getState()),然后loggerOneIn執(zhí)行完畢,執(zhí)行權(quán)交還到上一層loggerTwoIn,loggerTwoIn繼續(xù)執(zhí)行,執(zhí)行console.log("loggertwo:", getState()),結(jié)束。愛掏網(wǎng) - it200.com


692ec77208bdbea7c8f72990141323745e34484c

畫一張圖形象的表示下執(zhí)行流程:
32a2fb4de0968a611e13fb45ebd88e675453c6d2
到此,applymiddleware方法就講完了,我們來看下redux官方源碼的實現(xiàn):


function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []

        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)

        return {
            ...store,
            dispatch
        }
    }
}

我們實現(xiàn)的applyMiddleware方法對比官方除了沒有對前后端同構(gòu)時預(yù)取數(shù)據(jù)preloadedState做支持外,其余功能都完整實現(xiàn)了。愛掏網(wǎng) - it200.com 到此我們把redux里所有方法都實現(xiàn)了一遍,當(dāng)然我們實現(xiàn)的只是每個方法最核心最常用的部分,并沒有將redux源碼逐字逐句去翻譯。愛掏網(wǎng) - it200.com因為個人認為對于源碼的學(xué)習(xí)應(yīng)該抓住主線,學(xué)習(xí)源碼中的核心代碼及閃光點,如果對redux其他功能感興趣的,可以自行看官方源碼學(xué)習(xí)。愛掏網(wǎng) - it200.com

接下來,我們將redux常用的三個中間件來實現(xiàn)一下

redux-logger



let logger = function({ dispatch, getState }) {
        return function(next) {
            return function(action) {
                console.log(getState());
                next(action)
                console.log(getState())
            }
        }
    }

這個我們上面講applyMiddleware時已經(jīng)講過了,不再多說。愛掏網(wǎng) - it200.com

redux-thunk

redux-thunk在我們平常使用時主要用來處理異步提交action情況,引入了redux-thunk后我們可以異步提交action


const fetchPosts = postTitle => (dispatch, getState) => {
    dispatch(requestPosts(postTitle));
    return fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
        .then(json => dispatch(receivePosts(postTitle, json)));
};
store.dispatch(fetchPosts('reactjs'))

我們可以看到fetchPosts('reactjs')返回的是一個函數(shù),而redux里的dispatch方法不能接受一個函數(shù),Redux官方源碼中明確說了,action必須是一個純粹的對象,處理異步action時需要使用中間件,
function dispatch(action) {
    if (!isPlainObject(action)) {
        throw new Error(
            'Actions must be plain objects. ' +
            'Use custom middleware for async actions.'
        )
    }
    ......
}

那redux-thunk到底做了什么使dispatch可以傳入函數(shù)呢?



let thunk = function({ getState, dispatch }) {
    return function(next) {
        return function(action) {
            if (typeof action == 'function') {
                action(dispatch, getState);
            } else {
                next(action);
            }
        }
    }
}

thunk中間件在內(nèi)部進行判斷,如果傳入了一個函數(shù),就去執(zhí)行它,不是函數(shù)就不管交給下一個中間件,以上面的fetchPosts為例,當(dāng)執(zhí)行store.dispatch(fetchPosts('reactjs'))時,給dispatch傳入了一個函數(shù):


postTitle => (dispatch, getState) => {
    dispatch(requestPosts(postTitle));
    return fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
        .then(json => dispatch(receivePosts(postTitle, json)));
};

thunk中間件發(fā)現(xiàn)是個函數(shù),于是執(zhí)行它,先發(fā)出一個Action(requestPosts(postTitle)),然后進行異步操作。愛掏網(wǎng) - it200.com拿到結(jié)果后,先將結(jié)果轉(zhuǎn)成 JSON 格式,然后再發(fā)出一個Action(receivePosts(postTitle,json))。愛掏網(wǎng) - it200.com這兩個Action都是普通對象,所以當(dāng)dispatch時會走else {next(action);}這個分支,繼續(xù)執(zhí)行.這樣就解決了dispatch不能接受函數(shù)的問題。愛掏網(wǎng) - it200.com

redux-promise

最后講一個redux-promise中間件.dispatch目前可以支持傳入函數(shù)了,利用redux-promise我們再讓它支持傳入promise對象,平時我們在用這個中間件時,一般有兩種用法: 寫法一,返回值是一個 Promise 對象。愛掏網(wǎng) - it200.com


const fetchPosts =
    (dispatch, postTitle) => new Promise(function(resolve, reject) {
        dispatch(requestPosts(postTitle));
        return fetch(`/some/API/${postTitle}.json`)
            .then(response => {
                type: 'FETCH_POSTS',
                payload: response.json()
            });
    });

寫法二,Action 對象的payload屬性是一個Promise對象。愛掏網(wǎng) - it200.com這需要從redux里引入createAction方法,并且寫法也要變成下面這樣。愛掏網(wǎng) - it200.com


import { createAction } from 'redux-actions';

class AsyncApp extends Component {
    componentDidMount() {
        const { dispatch, selectedPost } = this.props
            // 發(fā)出同步 Action
        dispatch(requestPosts(selectedPost));
        // 發(fā)出異步 Action
        dispatch(createAction(
            'FETCH_POSTS',
            fetch(`/some/API/${postTitle}.json`)
            .then(response => response.json())
        ));
    }
}

讓我們來實現(xiàn)一下redux-promise中間件:



let promise = function({ getState, dispatch }) {
        return function(next) {
            return function(action) {
                if (action.then) {
                    action.then(dispatch);
                } else if (action.payload && action.payload.then) {
                    action.payload.then(payload => dispatch({...action, payload }), payload => dispatch({...action, payload }));
                } else {
                    next(action);
                }
            }
        }
    }

我們實現(xiàn)redux-thunk時是判斷如果傳入function就執(zhí)行這個function,否則next(action)繼續(xù)執(zhí)行;redux-promise同理,當(dāng)action或action的payload上面有then方法時,我們認為它是promise對象,就讓dispatch到promise的then里面再執(zhí)行,直到dispatch提交的action沒有then方法,我們認為它不是promise了,可以執(zhí)行next(action)交給下一個中間件執(zhí)行了。愛掏網(wǎng) - it200.com

最后

本篇介紹了Redux五個方法createStore,applyMiddleware,bindActionCreators,combineReducers,compose的實現(xiàn)原理,并自己封裝了一個小巧完整的Redux庫,同時簡單介紹了Redux里常用的3個中間件redux-logger,redux-thunk,redux-promise的實現(xiàn)原理,本文所有代碼在github建有代碼倉庫,可以點擊查看本文源碼。愛掏網(wǎng) - it200.com

與Redux相關(guān)的比較經(jīng)典的輪子還有React-Redux和redux-saga,因本文篇幅現(xiàn)在已經(jīng)很長,所以這兩個輪子的實現(xiàn)將放到后續(xù)的一起學(xué)習(xí)造輪子系列中,敬

聲明:所有內(nèi)容來自互聯(lián)網(wǎng)搜索結(jié)果,不保證100%準(zhǔn)確性,僅供參考。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系我們進行處理。
發(fā)表評論
更多 網(wǎng)友評論0 條評論)
暫無評論

返回頂部

主站蜘蛛池模板: 2021光根影院理论片| 精品无码一区二区三区爱欲| 亚洲国产精品专区| 在线中文字幕日韩欧美| 精品人妻中文无码AV在线| 久久99精品视香蕉蕉| 国产免费的野战视频| 日本狂喷奶水在线播放212| 欧美日韩一区二区不卡三区| 忘忧草www日本| 97精品在线观看| 中文免费观看视频网站| 国产成人综合久久精品亚洲| 精品无码人妻一区二区三区| 亚洲av永久无码精品秋霞电影影院| 妞干网免费视频| 蜜臀av无码人妻精品| 九九在线观看精品视频6| 国产成人免费高清激情视频 | 欧美一级视频在线高清观看| 国产香蕉国产精品偷在线| 亚洲欧美综合一区| 2022国内精品免费福利视频| 欧美一级黄色片在线观看| 国产日韩精品一区二区在线观看| 久久精品中文字幕一区| 色天天综合色天天看| 巨大欧美黑人xxxxbbbb| 产传媒61国产免费| 4480yy私人影院论| 最近中文字幕免费mv视频7| 国产在线观看免费视频软件| 久久99久久99精品免视看动漫| 美国农夫激情在线综合| 天天做天天爱天天干| 亚洲国产日韩在线一区| 麻豆成人精品国产免费| 成年女人18级毛片毛片免费| 免费人成在线观看视频高潮| 8天堂资源在线官网| 日韩欧美卡一卡二卡新区|