從0實現一個tiny-redux

從0實現一個tiny-redux

講真,redux已經很小了,去掉註釋代碼也就300行吧,註釋寫的也是很是詳細了。 redux 更多的是對思惟上的變化:數據改變 + 視圖更新 兩者分開,各自管理本身。javascript

如今,讓咱們從無到有!!html

so tiny !

redux 是這樣的一個流程:觸發一個action --> redux作一些邏輯,返回state --> 觸發監聽程序。 因此一個 最小的redux:java

class Store {
    constructor(reducer, state = {}) {
        this.state = state
        this.listeners = []
        this.reducer = reducer
    }

    dispatch(action) {
        this.state = this.reducer(this.state, action)
        this.listeners.forEach(listener => listener())
    }

    getState() {
        return this.state
    }

    subscribe(listener) {
        this.listeners.push(listener)
    }
}
複製代碼

咱們的這個 Store 和 redux的store提供了想的api:react

  1. dispatch 觸發一個action
  2. getState 返回當前狀態
  3. subscribe 增長一個監聽器

讓咱們用這個最小的例子實現一個 計數器在線地址git

function reducer(state, action) {
   switch (action.type) {
       case 'addOne': {
           return {
               ...state,
               count: state.count + 1
           }
       }
       default: {
           return state
       }
   }
}

const store = new Store(reducer, {count: 0})

store.subscribe(() => {
    console.log('subscribe test:', store.getState())
})

store.dispatch({type: 'addOne'})
store.dispatch({type: 'addOne'})
複製代碼

另外一個靈魂 middleware

redux的中文文檔 上關於middleware的部分, 已經講的很好了。如今咱們從另外一個角度來看這個問題, 首先,middleware 是redux在dispatch先後,提供的擴展機制。 好比日誌功能, 須要在dispath一個action以前記錄一下狀態,而後reducer處理完邏輯以後, 再次記錄一下。 這不就是 面向切面編程嗎! 時髦的AOP! 用java的話無論是 靜態代理仍是動態代理, 寫起來都挺複雜的。 可是js實現 很簡單:github

function enhancer(originF) {
  return function(...args) {
    console.log('before')
    var result = originF(...args)
    console.log('after')
    return result
  }
}
複製代碼

enhancer 方法接受一個方法A, 返回一個加強的方法B。 對B咱們能夠再次 加強,因此這裏是能夠鏈式調用的:npm

var fEnhancer = function (originF) {
    return function (...args) {
        console.log('this is fEnhancer before')
        var r = originF(...args)
        console.log('this is fEnhancer after')
        return r
    }
}

var hEnhancer = function (originF) {
    return function (...args) {
        console.log('this is hEnhancer before')
        var r = originF(...args)
        console.log('this is hEnhancer after')
        return r
    }
}

var gEnhancer = function (originF) {
    return function (...args) {
        console.log('this is gEnhancer before')
        var r = originF(...args)
        console.log('this is gEnhancer after')
        return r
    }
}

function justPrint() {
    console.log('justPrint...')
}

fEnhancer(hEnhancer(gEnhancer(justPrint)))()

//這個例子輸出
this is fEnhancer before
this is hEnhancer before
this is gEnhancer before
justPrint...
this is gEnhancer after
this is hEnhancer after
this is fEnhancer after
複製代碼

對於 fEnhancer(hEnhancer(gEnhancer(justPrint))) 等效的寫法以下:編程

var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]
function enhancerFun(originF) {
    let of = originF
    enhancerArray.forEach(enhancer => {
        of = enhancer(of)
    })
    return of
}
複製代碼

更加流弊的寫法, 也就是redux的實現(巧妙的使用了數組的reduce方法):redux

var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]
function enhancerFun2(originF) {
    return enhancerArray.reduce((a, b) => (...args) => a(b(...args)))(originF)
}
複製代碼

回到 redux, 須要咱們加強的是dispatch, 因此只須要 enhancerFun(store.dispatch)。 這裏有兩個問題: 第一個問題 因爲咱們的dispatch裏面使用了 this, 而這個加強的調用: var r = originF() 這裏就丟掉了this。解決方法以下:api

class Store {
    constructor(reducer, state) {
        this.state = state
        this.listeners = []
        this.reducer = reducer

        this.dispatch = this.dispatch.bind(this)
        this.getState = this.getState.bind(this)
        this.subscribe = this.subscribe.bind(this)
    }
    ...
}
複製代碼

這樣在任何地方調用 store的方法, this 都沒有問題了

第二個問題:在gEnhancer 裏面咱們想要調用 store.getState() 來記錄 調用dispatch 先後的狀態怎麼辦? (咱們不可能每次去import store吧, 由於在寫enhancer的時候, 可能壓根就不知道 store在哪裏呢。 ) 方法以下:

var fEnhancer = function ({ getState, dispatch }) {
    return function (originF) {
        return function (...args) {
            console.log('this is fEnhancer before', getState())
            var r = originF(...args)
            console.log('this is fEnhancer after', getState())
            return r
        }
    }
}
複製代碼

經過閉包的形式, 咱們讓 fEnhancer 內部的邏輯 能夠直接使用 getState。

那middleware是什麼呢? 實際上, 這裏的fEnhancer就是標準的一個 redux middleware, 是的,redux-logger能夠不用了, 讓咱們用fEnhancer來記錄日誌吧。

對應的 applyMiddleware:

function applyMiddleware(store, ...args) {
    console.log(args)
    const enArr = args.map(middleware => middleware({
        getState: store.getState,
        dispatch: store.dispatch
    }))


    let of = store.dispatch
    enArr.forEach(en => {
        of = en(of)
    })

    store.dispatch = of
}
複製代碼

如今, 給咱們開頭的reducer 加強一下吧!! 在線地址

輔助函數

到這裏, tineyredux其實已經結束了。 可是redux爲了方便開發者 提供了兩個輔助函數: combineReducers 和 bindActionCreators。 bindActionCreators 就是在 本來調用 actionCreator的時候,默認幫你dispatch一下: actionCreator() ==》 store.dispatch(actionCreator())。 也能夠理解爲 '加強':

function bindActionCreator(creator, dispatch) {
    return function (...args) {
        dispatch(creator(args)) // <---- 也能夠理解爲 '加強'
    }
}

export default function bindActionCreators(creators, dispatch) {
    const keys = Object.keys(creators)
    const result = {}
    keys.forEach(key => {
        result[key] = bindActionCreator(creators[key], dispatch)
    })
    return result
}
複製代碼

combineReducers 是爲了解決另外的痛點, 好比以下的store 和reducer:

{
    clock: {
        count: 0
    },
    yk: {
        age: 0
    }
    ...
}

function reducer(state, action) {
    switch (action.type) {
        case 'clock_add':...
        case 'clock_cnum'...
        case 'yk_older': ...
        case 'yk_forever18': ...
        default: {
            return state
        }
    }
}
複製代碼

大部分狀況, 咱們發現咱們的應用,clock數據部分,對應clock本身的邏輯, yk數據部分的修改邏輯也只會關心本身(一般這都是2個頁面的數據了)。 因此這裏的一個 "大switch" 是能夠切分的。

function clockReducer(state, action) {
    switch (action.type) {
        case 'clock_addOne': ...
        case 'clock_cnum': ...
        default: {
            return state
        }
    }
}

function ykReducer(state, action) {
    switch (action.type) {
        case 'yk_older': ...
        case 'yk_forever18': ...
        default: {
            return state
        }
    }
}

function reducer(state, action) {
  return {
      clock: clockReducer(state, action),
      yk: ykReducer(state, action),
  }
}
複製代碼

combineReducers 就是對小的reducer進行合併的:

function combineReducers(reducers) {
    return function (state, action) {
        const keys = Object.keys(reducers)

        const newState = {}
        keys.forEach(key => {
            newState[key] = reducers[key](state[key], action)
        })
        return newState
    }
}
複製代碼

題外話: 這裏的 combineReducers 若是小reducer特別多, 會有一些性能問題: 由於對於每個 action,都是走了全部的reducer。 若是咱們場景特殊, 是咱們剛纔說的 一塊數據的邏輯 只對於一個reducer, 可使用下面的變種(只會執行一個reducer, 須要保證action前綴和store中key一致):

function combineReducersVariant(reducers) {
    return function (state, action) {
        const lineIndex = action.type.indexOf("_")
        const actionKey = action.type.substring(0, lineIndex)
        const newS = reducers[actionKey](state[actionKey], action)

        return state[actionKey] === newS ? state : {
            ...state,
            [actionKey]: newS
        }
    }
}
複製代碼

這裏有一個完整的保護 middleware, bindActionCreators, combineReducers 全部特性的完整的例子

代碼託管在git

安裝: npm install tiny-redux --save

相關文章
相關標籤/搜索