簡介: 手寫實現redux基礎apireact
api回顧:redux
createStore(reducer, [preloadedState], enhancer)api
建立一個 Redux store 來以存放應用中全部的 state reducer (Function): 接收兩個參數,當前的 state 樹/要處理的 action,返回新的 state 樹 preloadedState: 初始時的 state enhancer (Function): store creator 的高階函數,返回一個新的強化過的 store creator
Store 方法數組
getState() 返回應用當前的 state 樹 dispatch(action) 分發 action。這是觸發 state 變化的唯一途徑 subscribe(listener) 添加一個變化監聽器。每當 dispatch action 的時候就會執行,state 樹中的一部分可能已經變化 replaceReducer(nextReducer) 替換 store 當前用來計算 state 的 reducer(高級不經常使用,不做實現)實現 Redux 熱加載機制會用到
源碼實現:app
./self-redux.js export function createStore(reducer, enhancer) { if(enhancer) { return enhancer(createStore)(reducer) } let currentState = {} let currentListeners = [] function getState() { return currentState } function subscribe(listeners) { currentListeners.push(listener) } function dispatch(action) { currentState = reducer(currentState, action) currentListeners.forEach(v => v()) return action } dispatch({ type: '@rainie/init-store' }) return { getState, subscribe, dispatch } }
demo:驗證正確性dom
// import { createStore } from 'redux' // 將redux文件替換成本身實現的redux文件 import { createStore } from './self-redux.js' // 這就是reducer處理函數,參數是狀態和新的action function counter(state=0, action) { // let state = state||0 switch (action.type) { case '加機關槍': return state + 1 case '減機關槍': return state - 1 default: return 10 } } // 新建store const store = createStore(counter) const init = store.getState() console.log(`一開始有機槍${init}把`) function listener(){ const current = store.getState() console.log(`如今有機槍${current}把`) } // 訂閱,每次state修改,都會執行listener store.subscribe(listener) // 提交狀態變動的申請 store.dispatch({ type: '加機關槍' })
api簡介異步
把一個由多個不一樣 reducer 函數做爲 value 的 object,合併成一個最終的 reducer 函數 實現 Redux 熱加載機制會用到
import { combineReducers } from 'redux' import todos from './todos' import counter from './counter' export default combineReducers({ todos, counter })
實現:ide
實質就是返回一個大的function 接受state,action,而後根據key用不一樣的reducer
注:combinedReducer的key跟state的key同樣函數
const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c }) function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } }
function combindReducer(reducers) { // 第一個只是先過濾一遍 把非function的reducer過濾掉 const reducerKeys = Object.keys(reducers) const finalReducers = {} reducerKeys.forEach((key) => { if(typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } }) const finalReducersKeys = Object.keys(finalReducers) // 第二步比較重要 就是將全部reducer合在一塊兒 // 根據key調用每一個reducer,將他們的值合併在一塊兒 let hasChange = false; const nextState = {}; return function combind(state={}, action) { finalReducersKeys.forEach((key) => { const previousValue = state[key]; const nextValue = reducers[key](previousValue, action); nextState[key] = nextValue; hasChange = hasChange || previousValue !== nextValue }) return hasChange ? nextState : state; } }
applyMiddleware(...middleware)spa
使用包含自定義功能的 middleware 來擴展 Redux 是 ...middleware (arguments): 遵循 Redux middleware API 的函數。 每一個 middleware 接受 Store 的 dispatch 和 getState 函數做爲命名參數,並返回一個函數。 該函數會被傳入 被稱爲 next 的下一個 middleware 的 dispatch 方法,並返回一個接收 action 的新函數,這個函數能夠直接調用 next(action),或者在其餘須要的時刻調用,甚至根本不去調用它。 調用鏈中最後一個 middleware 會接受真實的 store 的 dispatch 方法做爲 next 參數,並藉此結束調用鏈。 因此,middleware 的函數簽名是 ({ getState, dispatch }) => next => action
import { createStore, combineReducers, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import * as reducers from './reducers' let reducer = combineReducers(reducers) // applyMiddleware 爲 createStore 注入了 middleware: let store = createStore(reducer, applyMiddleware(thunk))
中間件機制圖
實現步驟
1.擴展createStore,使其接受第二個參數(中間件其實就是對createStore方法的一次擴展)
2.實現applyMiddleware,對store的disptach進行處理
3.實現一箇中間件
正常調用
import React from 'react' import ReactDOM from 'react-dom' // import { createStore, applyMiddleware} from 'redux' import { createStore, applyMiddleware} from './self-redux' // import thunk from 'redux-thunk' import thunk from './self-redux-thunk' import { counter } from './index.redux' import { Provider } from './self-react-redux'; import App from './App' const store = createStore(counter, applyMiddleware(thunk)) ReactDOM.render( ( <Provider store={store}> <App /> </Provider> ), document.getElementById('root'))
// 便於理解:函數柯利化例子 function add(x) { return function(y) { return x+y } } add(1)(2) //3
applymiddleware
// ehancer(createStore)(reducer) // createStore(counter, applyMiddleware(thunk)) // applyMiddleware(thunk)(createStore)(reducer) // 寫法函數柯利化 export function applyMiddleware(middleware) { return function (createStore) { return function(...args) { // ... } } } // 只處理一個 middleware 時 export function applyMiddleware(middleware) { return createStore => (...args) => { const store = createStore(...args) let dispatch = store.dispatch const midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 通過中間件處理,返回新的dispatch覆蓋舊的 dispatch = middleware(midApi)(store.dispatch) // 正常中間件調用:middleware(midApi)(store.dispatch)(action) return { ...store, dispatch } } } // 處理多個middleware時 // 多個 compose export function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = store.dispatch const midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const middlewareChain = middlewares.map(middleware => middleware(midApi)) dispatch => compose(...middlewareChain(store.dispatch)) // dispatch = middleware(midApi)(store.dispatch) // middleware(midApi)(store.dispatch)(action) return { ...store, dispatch } } }
手寫redux-thunk異步中間件實現
// middleware(midApi)(store.dispatch)(action) const thunk = ({ dispatch, getState }) => next => action => { // next就是store.dispatch函數 // 若是是函數,執行如下,參數dispatch和getState if (typeof action == 'function') { return action(dispatch, getState) } // 默認 什麼都不幹 return next(action) } export default thunk 處理異步action export function addGunAsync() { // thunk插件做用,這裏能夠返回函數 return dispatch => { setTimeout(() => { // 異步結束後,手動執行dispatch dispatch(addGun()) }, 2000) } }
趁熱打鐵,再實現一箇中間件: dispatch接受一個數組,一次處理多個action
export arrayThunk = ({ dispatch, getState }) => next => action => { if(Array.isArray(action)) { return action.forEach(v => dispatch(v)) } return next(action) } 這類action會被處理 export function addTimes() { return [{ type: ADD_GUN },{ type: ADD_GUN },{ type: ADD_GUN }] }
在react-redux connect mapDispatchToProps中使用到了該方法,能夠去看那篇blog,有詳解~
api: bindActionCreators(actionCreators, dispatch)
把 action creators 轉成擁有同名 keys 的對象,但使用 dispatch 把每一個 action creator 包圍起來,這樣能夠直接調用它們
實現:
function bindActionCreator(creator, dispatch) { return (...args) => dispatch(creator(...args)) } export function bindActionCreators(creators, dispatch) { let bound = {} Object.keys(creators).forEach( v => { let creator = creators[v] bound[v] = bindActionCreator(creator, dispatch) }) return bound } // 簡寫 export function bindActionCreators(creators, dispatch) { return Object.keys(creators).reduce((ret, item) => { ret[item] = bindActionCreator(creators[item], dispatch) return ret }, {}) }
api: compose(...functions)
從右到左來組合多個函數。 當須要把多個 store 加強器 依次執行的時候,須要用到它
import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import DevTools from './containers/DevTools' import reducer from '../reducers' const store = createStore( reducer, compose( applyMiddleware(thunk), DevTools.instrument() ) )
實現:
compose(fn1, fn2, fn3)
fn1(fn2(fn3))
export function compose(...funcs) { if(funcs.length == 0) { return arg => arg } if(funcs.length == 1) { return funcs[0] } return funcs.reduce((ret,item) => (...args) => ret(item(...args))) }
原文地址: