redux 和 react-redux 部分源码阅读
從源代碼的入口文件發現,其實 redux 最終就只是導出了一個對象,對象中有幾個方法,代碼如下:
export {createStore,combineReducers,bindActionCreators,applyMiddleware,compose,__DO_NOT_USE__ActionTypes } 復制代碼所以重點分析幾個方法:
createStore 方法
方法中定義的一些變量:
let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false 復制代碼這些變量會被 dispatch 或者別的方法引用,從而形成閉包。這些變量不會被釋放。
創建 srore 的方法最終返回的是一個對象。對象中含有比較重要的方法dispatch,subscribe,getState。
return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable } 復制代碼其中 createStore 的第三個參數是應用中間件來做一些增強操作的。
if (typeof enhancer !== 'undefined') { // 如果增強方法存在就對 createStore 進行增強if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState) } 復制代碼subscribe 方法
其中 subscribe 用來注冊監聽方法,每一次注冊后會將監聽方法維護到數組currentListeners中,currentListeners 是 createStore 中的一個變量,由于被 subscribe 引用著所以形成了一個閉包。也就是通過閉包來維護狀態。
let currentListeners = [] 復制代碼dispatch 方法
dispatch 方法用來分發 action, 函數里面會生成新的 currentState, 會執行所有注冊了的函數。
核心代碼:
try {isDispatching = truecurrentState = currentReducer(currentState, action) // 生成新的 state } finally {isDispatching = false }const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener() } // 遍歷執行注冊函數 復制代碼getState
僅僅用來獲得當前的 state:
function getState() {return currentState } 復制代碼combineReducers 函數
函數中定義的一些變量,
const finalReducers = {} const finalReducerKeys = Object.keys(finalReducers) 復制代碼這個函數最后返回的是一個函數 combination, 返回的函數中引用了 finalReducers 和 finalReducerKeys,形成了閉包。
出于業務場景考慮,不同的模塊采用不同的 reducer 進行處理,所以 reducer 函數有很多。這些 reducer 會遍歷執行。
每一次 dispatch 一個 action 的時候就會執行
currentState = currentReducer(currentState, action) // 生成新的 state 復制代碼這里的 currentReducer 就是返回的 combination 函數。combination 函數中的核心代碼:
function combination(state = {}, action) {...let hasChanged = false// 每一次 reducer 執行的時候都會生成一個新的對象來作為新的 state const nextState = {}// 通過 for 循環遍歷 reducer for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]// 獲取當前的 stateconst previousStateForKey = state[key]// 執行相應的 reducer 后會生成新的 stateconst nextStateForKey = reducer(previousStateForKey, action)if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}// 給新的 state 賦值nextState[key] = nextStateForKey// 如果是一個簡單類型比如 string,number // 如果前后值一樣就不會觸發改變// 但如果 state 中某個值是一個對象,// 盡管前后對象中的值一樣,但是引用地址變化,還是會觸發改變hasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 所以如果簡單值沒有變化并且沒有對象的引用地址改變就會返回原來的 statereturn hasChanged ? nextState : state } 復制代碼結合 react-redux 中向 redux 訂閱的方法發現
subscribe() {const { store } = this.props // 這里的 store 是 createStore 方法執行后返回的對象this.unsubscribe = store.subscribe(() => { // 通過訂閱方法注冊監聽事件const newStoreState = store.getState() // 獲取新的 stateif (!this._isMounted) {return}// 通過使用函數替代對象傳入 setState 的方式能夠得到組件的 state 和 props 屬性可靠的值。this.setState(providerState => {// 如果值是一樣的就不會觸發更新if (providerState.storeState === newStoreState) {return null}return { storeState: newStoreState }})})// Actions might have been dispatched between render and mount - handle thoseconst postMountStoreState = store.getState()if (postMountStoreState !== this.state.storeState) {this.setState({ storeState: postMountStoreState })} } 復制代碼在注冊的 listen 方法中會發現如果最 新的state和原來的state一樣 就不會觸發 setState 方法的執行,從而就不會觸發 render。
applyMiddleware 使用中間件
源碼:
export default function applyMiddleware(...middlewares) {return createStore => (...args) => { // 接收 createStore 函數作為參數const store = createStore(...args)let dispatch = () => {throw new Error(`Dispatching while constructing your middleware is not allowed. ` +`Other middleware would not be applied to this dispatch.`)}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)} // 中間件函數接收的 API 參數,能夠獲取到當前的 state 和 createStore 函數的參數// 所以這里就向中間件函數中傳遞了參數const chain = middlewares.map(middleware => middleware(middlewareAPI))// 通過函數組合生成一個新的 dispatch 函數dispatch = compose(...chain)(store.dispatch)return {...store,dispatch} // 這里返回的是最后生成的 store,相比不使用中間件的區別是對 dispatch 進行了增強。} } 復制代碼結合 createStore 中的源碼:
return enhancer(createStore)(reducer, preloadedState) 復制代碼所以上面 applyMiddleware 中返回的函數就是這里的 enhancer 方法,接收 createStore 作為參數。
(reducer, preloadedState) 對應著中間件中的 (...args)。
react-redux
react-redux 通過提供 Provider 組件將 store 和整個應用中的組件聯系起來。確保整個組件都可以獲得 store, 這是通過 Context 來實現的。
Provider 組件最終渲染的組件:
render() {const Context = this.props.context || ReactReduxContextreturn (<Context.Provider value={this.state}>{this.props.children}</Context.Provider>) } 復制代碼其中 state 的定義如下:
const { store } = props this.state = {storeState: store.getState(),store } 復制代碼所以 Provider 給應用提供 store 的寫法如下,屬性名必須是 store。
<Provider store={store}><Router /> </Provider> 復制代碼redux-thunk
redux-thunk 是一個中間件,直接看中間件的源代碼是絕對不可能看明白的。
中間件不是一個完整的個體。它是為了豐富或者擴展某個模塊而出現的,其中會調用一些原來的模塊的方法,所以如果不看源模塊的對應的方法實現,根本無法理解。
所以要想看懂一個中間件,必須結合源模塊的代碼一起看。
Thunk 函數的含義和用法
JavaScript 語言是傳值調用,它的 Thunk 函數含義有所不同。在 JavaScript 語言中,Thunk 函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數作為參數。
如何讓 dispatch 分發一個函數,也就是 action creator??
dispatch 的參數只能是一個普通的對象,如果要讓參數是一個函數,需要使用中間件 redux-thunk。
設計思想就是一種面向切面編程AOP,對函數行為的增強,也是裝飾模式的使用。
redux-thunk 源碼:
function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);}; }const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware;export default thunk; 復制代碼如果只是用了 thunk,那么最終增強版的 dispatch 就是
action => {// 當 dispatch 參數是一個函數的時候執行這里if (typeof action === 'function') { // 這里的 dispatch 就是最原始的 dispatch// 所以 action 函數中可以直接使用參數 dispatch 和 getState 函數return action(dispatch, getState, extraArgument);}return next(action); // 這里的 next 是 store.dispatch } 復制代碼異步操作帶代碼
異步操作如果使用 action creator, 則至少要送出兩個 Action:
- 用戶觸發第一個 Action,這個跟同步操作一樣,沒有問題;
- 在 action creator 函數中送出第二個 Action
代碼實例:
handleClick = () => {const { dispatch } = this.propsdispatch(this.action); // 發出第一個 action(函數) }action = (dispatch, getState) => setTimeout(() => {dispatch({ type: 'REQUESTSTART' }) }, 1000) // 發出第二個 action(普通對象) 復制代碼思考
異步代碼的處理一定要使用 redux-thunk嗎?
非也。在觸發含有異步代碼的函數執行時,把 dispatch 函數作為一個參數傳給函數,然后這個異步函數里面在合適的時機調用 dispatch 發出 action 就行。
上面的異步代碼可改寫如下:
handleClick = () => {const { dispatch } = this.propsthis.action(dispatch); }action = dispatch => setTimeout(() => {dispatch({ type: 'REQUESTSTART' }) }, 1000) 復制代碼不過相比 redux-thunk 有個缺陷就是不能獲取 getState 這個方法。
使用示例
使用 redux 演示代碼
轉載于:https://juejin.im/post/5c26e210e51d45342a255ad2
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的redux 和 react-redux 部分源码阅读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OSS-视频截帧
- 下一篇: 微软宣布Azure Function支持