前言
前陣子寫的Redux解析感覺寫不清楚,因此重寫一篇如何實作Redux。
這邊必須對Redux有些基本認識,而實作的部分都先不做錯誤處理。
createStore
首先從createStore的部分開始,在實做createStore前先如同往常寫好一個reducer並把他傳入createStore中,並對他發出一個有效的action。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const reducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload ; default : return state ; } } let store = createStore(reducer) ; console.log(store.getState()); store.dispatch({ type : 'ADD', payload : 1 }); console.log(store.getState());
|
接下來開始實做createStore,首先先建立getState和dispatch函式。
1 2 3 4 5 6 7 8 9 10 11 12
| const createStore = () => { const getState = () => { } const dispatch = () => { } return { getState , dispatch } }
|
接下來紀錄傳進來的reducer和state。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { } const dispatch = () => { } return { getState , dispatch } }
|
getState
getState只要簡單回傳nowState即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = () => { } return { getState , dispatch } }
|
dispatch
dispatch接受action把它丟給reducer處理,並將nowState替換成運算後的state。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } return { getState , dispatch } }
|
為了有初始狀態,先發送一個不會被處理的action。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch } }
|
如此一來簡單的createStore就可以運行了。
subscribe
subscribe讓你可以讓你綁定函式在dispatch後執行,同時回傳一個unsubscribe函式讓你可以取消綁定。
1 2 3 4 5 6 7 8 9 10
| const listener = () => { console.log('Now state: ',store.getState()) ; } let store = createStore(reducer) ; let unsubscribe = store.subscribe(listener) ; store.dispatch({ type : 'ADD' , payload : 1 }); unsubscribe(); store.dispatch({ type : 'ADD' , payload : 1 }); console.log(store.getState());
|
首先額外建立一個陣列來保存subscribe的所有函式,並建立subscribe函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListener = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; } const subscribe = () => { } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe } }
|
接下來在只要註冊事件就把該函式放入nowListener並回傳一個函式用來註銷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe } }
|
Redux在subscribe部分會維持兩個陣列,一個是上次dispatch後的註冊事件,另一個則是在下次dispatch前新註冊的所有事件,這兩個事件陣列會在dispatch後同步,在這邊不實作這部分。
replaceReducer
將傳進來的reducer和目前的reducer替換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const reducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload ; default : return state ; } } const newReducer = ( state = 0 , action ) => { switch ( action.type ){ case 'ADD': return state + action.payload * 2 ; default : return state ; } } let store = createStore(reducer) ; store.dispatch({ type : 'ADD' , payload : 1 }); store.replaceReducer(newReducer); store.dispatch({ type : 'ADD' , payload : 1 }); console.log(store.getState());
|
只要把nowReducer替換成傳進來的reducer即可,替換後要發出初始action來獲得初始狀態。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const createStore = (reducer,state) => { let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } }
|
如此一來基本的createStore就實作完成了,接下來實作combineReducer的部分。
combineReducer
由於reducer可能不只一個,因此必須將reducers合併為一個。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const personReducer = ( state = {} , action ) => { switch ( action.type ){ case 'NAME': return {...state, name : action.payload }; case 'AGE': return {...state, age : action.payload } ; default : return state ; } } const todoReducer = ( state = [] , action ) => { switch ( action.type ){ case 'ADD': return [...state,action.payload] ; default : return state ; } } let reducer = combineReducer({ personReducer , todoReducer }) let store = createStore(reducer) ; store.dispatch({ type : 'NAME' , payload : 'Jeno' }); store.dispatch({ type : 'AGE' , payload : 22 }); store.dispatch({ type : 'ADD' , payload : 'Coding' }); console.log(store.getState());
|
接下來實作combineReducer的部分,首先我們先回傳一個函式來代表合併的reducer
1 2 3 4 5
| const combineReducer = () => { return function(){ } }
|
接下來一樣如同一般的reducer一樣接受state及action,在這邊創建一個新的newState回傳。
1 2 3 4 5 6
| const combineReducer = (reducers) => { return function(state = {} , action){ let newState = {}; return newState ; } }
|
接下來就是遍歷整個reducers,並將傳入中action和相對應的state傳入該reducer得到的狀態放在newState並回傳newState即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| reducers = { personReducer : function( state = {} , action ){ switch ( action.type ){ case 'NAME': return {...state, name : action.payload }; case 'AGE': return {...state, age : action.payload } ; default : return state ; } }, todoReducer : function( state = [] , action ){ switch ( action.type ){ case 'ADD': return [...state,action.payload] ; default : return state ; } } } */ const combineReducer = (reducers) => { return function(state = {} , action){ let newState = {}; for ( let key in reducers ) { newState[key] = reducers[key](state[key],action) ; } return newState ; } }
|
bindActionCreators
bindActionCreators可以直接把創造action的函式直接和dispatch綁在一起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const setPersonName = (name) => { return { type : 'NAME' , payload : name } } const setPersonAge = (age) => { return { type : 'AGE' , payload : age } } const addTodo = (text) => { return { type : 'ADD' , payload : text } } const boundActionCreators = bindActionCreators({ setPersonName , setPersonAge , addTodo },store.dispatch) ; boundActionCreators.addTodo('Coding') ; boundActionCreators.setPersonAge(22); boundActionCreators.setPersonName('Jeno'); console.log(store.getState());
|
接下來是實作的部分,我們首先創建一個空物件boundActionCreators並回傳。
1 2 3 4
| const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; return boundActionCreators ; }
|
再來遍歷actionCreators並把相對應的key宣告為函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| actionCreators = { setPersonName : function(name){ return { type : 'NAME' , payload : name } }, setPersonAge : function(age){ return { type : 'AGE' , payload : age } }, addTodo : function(text){ return { type : 'ADD' , payload : text } } } */ const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; for ( let key in actionCreators ){ boundActionCreators[key] = () => { } } return boundActionCreators ; }
|
該函式的內容則是直接利用dispatch去發出該函式回傳的action。
1 2 3 4 5 6 7 8 9
| const bindActionCreators = (actionCreators,dispatch) => { let boundActionCreators = {} ; for ( let key in actionCreators ){ boundActionCreators[key] = (payload) => { dispatch(actionCreators[key](payload)) ; } } return boundActionCreators ; }
|
applyMiddleware
applyMiddleware可以將dispacth包裝起來,如同洋蔥一樣必須一層一層透過middlewares才會到最後的store.dispatch。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 1, my next is ',next) ; return next(action) ; } const middleware2 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 2, my next is ',next) ; return next(action); } let store = createStore(reducer,{},applyMiddleware(middleware1,middleware2)) ; store.dispatch({ type : 'NAME' , payload : 'Jeno '}) ; console.log(store.getState());
|
首先假設我們現在只有一個middleware,middleware傳入兩個參數dispatch和action如下,代表希望dispatch可以透過這個middleware做處理。
1 2 3 4 5 6 7
| const middleware = (dispatch,action) => { console.log('I am middleware, before dispatch'); dispatch(action); console.log('I am middleware, after dispatch'); } let store = createStore(reducer,{},middleware) ;
|
我們先創建另一個函式專門針對含有middleware的做處理,若要使用middleware則必須使用該函式。
1 2 3 4 5 6 7 8 9 10 11
| const createStoreWithMiddleWare = (reducer,state,middleware) => { } const middleware = (dispatch,action) => { console.log('I am middleware, before dispatch'); dispatch(action); console.log('I am middleware, after dispatch'); } let store = createStoreWithMiddleWare(reducer,{},middleware) ;
|
接下來實作createStoreWithMiddleWare的部分,先使用原本createStore創造store,將dispatch修改為將原先的dispatch和action傳入middleware做處理,再將修改後的store回傳。
1 2 3 4 5 6 7 8 9 10
| const createStoreWithMiddleWare = (reducer,state,middleware) => { let store = createStore(reducer,state) ; let dispatch = function(action){ middleware(store.dispatch,action) ; } return { ...store, dispatch } }
|
我們可以在createStore做處理,如此一來就不需要呼叫兩個不同的函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const createStore = (reducer,state,middleware) => { if ( middleware !== undefined ){ return createStoreWithMiddleWare(reducer,state,middleware) ; } let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } } let store = createStore(reducer,{},middleware) ;
|
若我們想要傳入多個middlewares,我們可以修改每一個middleware成新的函式,將下一個middleware帶進去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let newMiddleware = [...middlewares,store.dispatch] ; for ( let i = 0 ; i < newMiddleware.length - 1 ; i ++ ){ let fn = newMiddleware[i] ; newMiddleware[i] = function(action,dispatch){ fn(action,newMiddleware[i+1]) ; } } let dispatch = function(action){ newMiddleware[0](action,newMiddleware[1]) ; } return { ...store, dispatch } } const middleware1 = (action,dispatch) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); } const middleware2 = (action,dispatch) => { console.log('I am middleware 2, before dispatch'); dispatch(action); console.log('I am middleware 2, after dispatch'); }
|
我們在這邊把action和dispatch參數對調,是為了因應dispatch(action)第一個參數是action的原因。
或者,我們也可以在這邊先將middleware函式Curry化,如此一來我們就可以先傳入store.patch,之後再傳入action,以便於直接搭配compose來使用。
關於Curry可以看我另一篇文章:[JavaScript] Curry
關於Compose可以看我的另一篇文章:[JavaScript] Compose 和 Pipe
1 2 3 4 5 6 7 8 9 10 11
| const createStoreWithMiddleWare = (reducer,state,middleware) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; dispatch = function(action){ middleware(store.dispatch)(action) ; } return { ...store, dispatch } }
|
接著修改middleware的函式結構。
1 2 3 4 5
| const middleware = (dispatch) => (action) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); }
|
如果要傳入多個middlewares。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (dispatch) => (action) => { console.log('I am middleware 1, before dispatch'); dispatch(action); console.log('I am middleware 1, after dispatch'); } const middleware2 = (dispatch) => (action) => { console.log('I am middleware 2, before dispatch'); dispatch(action); console.log('I am middleware 2, after dispatch'); } let store = createStore(reducer,{},[middleware1,middleware2]) ;
|
修改createStoreWithMiddleWare,先compose所有的middlewares再傳入store.dispatch。
1 2 3 4 5 6 7 8 9 10 11 12
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let composedMiddleWare = compose(...middlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
帶入store.dispatch到Compose完Curry後的middlewares,可以經由每次回傳一個函式到上一個middleware,如此一來就可以組合成一個大的middleware使傳入的action可以透過middleware1->middleware2->store.dispatch。
接著,假設我們現在必須對特定的action做處理,例如
1 2 3 4 5 6 7 8 9
| store.dispatch(() => { setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
發現在action裡面的函式並沒有dispatch和getState可以使用,因此必須傳這兩個進來。
1 2 3 4 5 6 7 8 9 10 11
| store.dispatch((middlewareAPI) => { let dispatch = middlewareAPI.dispatch ; let getState = middlewareAPI.getState ; setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
如此一來修改createStoreWithMiddleWare,在這邊我們傳入帶dispatch和getState的middlewareAPI到每個middleware,注意在這邊帶的dispatch必須是修改過後的dispatch而不是store.dispatch,才可以確保每次dispatch都會從第一個middleware開始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const createStoreWithMiddleWare = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
再來我們必須修改middleware的架構,再這邊我們把middleware2修改成可以處理action是函式的狀況,同時這也是redux-thunk實作原理。
為了避免dispatch搞混,我們將前往下一個middleware的dispatch改為next。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const middleware1 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 1, going to next'); let result = next(action); } const middleware2 = (middlewareAPI) => (next) => (action) => { console.log('I am middleware 2, going to next'); if ( typeof action === 'function' ){ action(middlewareAPI) ; } else { let result = next(action); } }
|
因此每當我們呼叫一般的物件action,則會通過middleware1->middleware2->store.dispatch。
若action是函式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let store = createStore(reducer,{},[middleware1,middleware2]) ; console.log(store.getState()); store.dispatch({ type : 'NAME' , payload : 'Jeno '}) ; console.log(store.getState()); store.dispatch((middlewareAPI) => { let dispatch = middlewareAPI.dispatch ; let getState = middlewareAPI.getState ; setTimeout(()=>{ dispatch({ type : 'AGE' , payload : 22 }) ; console.log(getState()); },1000) }) ;
|
流程則是middleware1->middleware2->middleware1->middleware2->store.dispatch。
如此一來處理middlewares的函式其實差不多了,我們把參數傳遞方式改一下並且改createStoreWithMiddleWare為applyMiddleware。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const applyMiddleware = (reducer,state,middlewares) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } } let store = createStore(reducer,{},applyMiddleware(middleware1,middleware2)) ;
|
接著改寫applyMiddleware的結構,並讓它可以傳reducer和state進來。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const applyMiddleware = (...middlewares) => (reducer,state) => { let store = createStore(reducer,state) ; let dispatch = store.dispatch ; let middlewareAPI = { getState : store.getState , dispatch : (action) => dispatch(action) } let newMiddlewares = middlewares.map((middleware)=>middleware(middlewareAPI)); let composedMiddleWare = compose(...newMiddlewares)(store.dispatch); dispatch = function(action){ composedMiddleWare(action) ; } return { ...store, dispatch } }
|
在createStore的部分也做修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const createStore = (reducer,state,enhancer) => { if ( enhancer !== undefined ){ return enhancer(reducer,state) ; } let nowReducer = reducer ; let nowState = state ; let nowListeners = [] ; const getState = () => { return nowState ; } const dispatch = (action) => { nowState = nowReducer(nowState,action) ; nowListeners.forEach((listener) => listener()); } const subscribe = (listener) => { nowListeners.push(listener) ; return function(){ nowListeners.splice(nowListeners.indexOf(listener),1); } } const replaceReducer = (reducer) => { nowReducer = reducer ; dispatch({ type : 'INIT' }) ; } dispatch({ type : 'INIT' }) ; return { getState , dispatch , subscribe , replaceReducer } }
|
Redux會再把createStore傳進applyMiddleware裡,如此一來就不需要createStore這段程式了。