在react項目中,redux常常用來管理應用的數據,react-redux用來綁定redux, 這樣你的組件能夠從store中讀取數據,而且能夠dispatch actions更新store, redux主要思想讓系統中的數據按照統一的規則流動,即單項數據流:javascript
如圖: 對於組件的修改,經過action被dispatch到store, store根據action和當前的state計算出下一次的state, component拿到更新後的state從新渲染組件;能夠看到數據只是單項流動java
動手實現一個簡單的redux, react-redux, 中間件,而後對比一下咱們的實現有哪些潛在的問題,實際上怎麼解決的react
如何使用redux和react-redux, redux.createStore方法建立一個store, store注入到Provider組件上,而後經過connect方法拿到store中的state和dispatcher,而後經過組件的props拿到這些方法和屬性redux
createStore建立store,store對象具備dispatch方法,subscribe方法,getState方法, replaceReducer方法,那麼經過觀察者模式能夠實現數組
function createStore(reducer, preloadedState) { let listeners = [] let state = preloadedState let currentReducer = reducer let subscribe = (fn) => { listeners.push(fn) return () => { const index = nextListeners.indexOf(listener) listeners.splice(index, 1) } } let dispatch = (action) => { state = currentReducer(state, action) for (let i = 0; i< listeners.length; i++) { const listener = listeners[i] listener() } } let getState = () => { return state } let repalceReducer = (nextReducer) => { currentReducer = nextReducer dispatch({type:'@@redux/REPLACE'}) } return { dispatch subscribe, getState, replaceReducer } }
redux實際實現要比這個複雜,好比說listeners存在兩個變量中, currentListeners和nextListeners,dispatch執行的老是currentListeners中的函數,subscribe和unsubscibe老是在nextListeners中增長或者移除listener,這樣能夠避免在dispatching過程當中,listeners數組發生改變,上面還能夠看到replaceReducer其實更新了state,觸發了訂閱, replaceReducer用在須要按需加載的reducer場景中,看下combiceReducers的實現,你會發現一次combine幾百個reducer並非一件好事,replaceReducer動態替換reducer提高效率app
接下來的問題如何在組件中訂閱state的更新,而且能夠dispatch action,以更新state;redux-redux使用了Context, redux中的Provider組件就是對Context.Provider作了封裝ide
export const ReactReduxContext = React.createContext(null) function Provider({store, children, context}) { const [provider, setProvider] = useState({ state: store.getState(), dispatch: store.dispatch }) useEffect(() => { store.subcribe(() => { setProvider({ state: store.getState(), dispatch: store.dispatch }) }) }, [store]) const Context = context || ReactReduxContext return <Context>{props.children}</Context> } export default Provider
有了Provider組件,咱們還須要一個connect函數,connect函數接收mapStateToProps和mapDispatchToProps,返回一個高階組件,這個高階組件接收React組件,返回一個PureComponent;函數
import ReactReduxContext from './Provider' export default function connect (mapStateToProps, mapDispatchToProps) { return function (WrappedComment) { return function (props) { return ( <ReactReduxContext> { ({state, dispatch}) => { <WrappedComment {...mapStateToProps(state, props)} {...mapDispatchToProps(dispatch, props)} /> } } </ReactReduxContext> ) } } }
其實這個返回的組件,react-redux又用React.memo進行包裝,保證只在props發生變化的時候纔會從新渲染。react-redux對於connect的實現比這裏要複雜得多,如開頭提出的問題: react-redux須要保證父組件的更新在子組件以前,react的connect方法實際上是對connectAdvanced方法的封裝(參見官網),connectAdvanced方法放回一個高階組件,如上面所封裝的connect方法,返回的組件又使用React.memo變爲PureComponent, react-redux如何保證父組件訂閱store的更新,發生在子組件以前呢? 也是經過subscirpe方法;每個connect方法返回的高階組件內部都用了一個Context.Provider包裝WrappedComponent, 她的value爲store和Subscription對象,這樣每個子組件的Subscription對象能夠拿到離它最近的父組件的Subscription對象,這樣造成了一個Subscription對象樹,經過Subvcription控制更新順序, 直接上圖spa
什麼是中間件?中間件就是對dispatch方法作了封裝,好比說每次dispatch一個action前,我須要發送一條日誌,若是不使用中間件,你的作法是日誌