redux源碼解析
1、首先讓我們看看都有哪些內容

2、讓我們看看redux的流程圖

Store:一個庫,保存數據的地方,整個項目只有一個
創建store
Redux提供 creatStore 函數來生成 Store
// 引入redux import { createStore } from 'redux'; //創建Store 需要傳遞一個函數fn 這里的fn是之后會提及的reducers const store = createStore(fn);
State:狀態,某時刻的數據即是Store的狀態
獲取狀態的方法是store.getState()
Action:行為,它有一個不可或缺的type屬性
action還可以攜帶其他內容
我們可以使用action來改變State的值,
從而將我們需要的數據通過Action“運輸”到 Store;
dispatch:發送action
dispatch(action)接受一個action對象為參數,並將它發送出去,
Store接受Action,接受之后需要返回一個新的State(狀態)
Reducer:處理器
dispatch(action)接受一個action對象為參數,並將它發送出去,
Store接受Action,接受之后需要返回一個新的State(狀態)
而創建這個新的狀態的過程就是reducer
3、從isPlainObject.js開始
/** * @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */ export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
· 這個函數的核心思想在於什么呢?
在於判斷一個值是否為一個普通的對象
此處的普通對象指的是直接通過字面量(let obj={})或者new Object()創建出來的對象
· 那么他是怎么做判斷的呢?
if (typeof obj !== 'object' || obj === null) return false
這行代碼排除掉肯定不是對象的值
注意:typeof null 的返回值為 "object". 所以只使用 typeof obj !== 'object' 不能將 null 值排除掉.
因此應使用 typeof obj !== 'object' || obj === null 進行判斷.
再往下就是通過原型鏈判斷了.
通過 while 不斷地判斷 Object.getPrototypeOf(proto) !== null 並執行,
最終 proto 會指向 Object.prototype. 這時再判斷 Object.getPrototypeOf(obj) === proto,
如果為 true 的話就代表 obj 是通過字面量或調用 new Object() 所創建的對象了.
Object.getPrototypeOf() 方法用於獲取一個對象的原型屬性指向的是哪個對象.
舉個🌰: 假設有一個構造器:function Fun(){} 創建一個對象:var f = new Fun() Object.getPrototypeOf(f) 得到的返回值 和訪問 f.__proto__ 是一樣的 這個值指向 Fun.prototype.
假如一個對象是普通對象
那么這個對象的 __proto__ 一定是指向 Object.prototype 的,
而非普通對象, 例如 f, 其 __proto__ 是指向其構造函數的 prototype 屬性.
因此比較 Object.getPrototypeOf(obj) 與 proto 相等, 則判定 obj 是普通對象.
4、接下來是createStore.js
//如果第二個參數為方法且第三個參數為空,則將兩個參數交換
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }
//不支持這樣寫
里面的幾個函數
getState()
//返回當前state樹
function getState() { if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) } return currentState }
subscribe()
//這個函數用於給store添加監聽函數,把需要添加的監聽函數作為參數傳入即可
//nextListeners 即為目前的監聽函數列表,添加了之后,subscribe方法會返回一個unsubscribe()方法
//此方法用於注銷剛才添加的監聽函數。
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
dispatch()
function dispatch(action) { //action必須是一個包含type的對象 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } //如果正處於isDispatching狀態,報錯 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true //這里就是調用我們reducer方法的地方,返回一個新的state作為currentState currentState = currentReducer(currentState, action) } finally { isDispatching = false } //調用所有的監聽函數 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
在這些執行完之后,dispatch方法會遍歷當前的監聽列表,並執行所有的監聽函數。
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
替換reducer之后重新初始化狀態樹
//是一種觀察者模式的思想
function observable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer) { if (typeof observer !== 'object' || observer === null) { throw new TypeError('Expected the observer to be an object.') }
//觀察者模式的鏈式結構,傳入當前的state
function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } }
5、接下來就是compose.js
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }
reduce方法接受2個參數,第一個參數是一個callback函數,第二個是一個初始值initValue
第一個函數有四個參數
- previousValue: 上一次調用callback時返回的值
- currentValue: 正在處理的數組元素
- index: 正在處理的數組元素下標
- array: 被處理的數組
如果有initValue,initValue將作為第一次的previousValue,若沒有,則數組第一個元素將作為previousValue,
后面一個元素將作為currentValue,然后執行callback的函數體,將返回的值作為previousValue,
將下一個元素作為currentValue,一直到最后一個數組最后一個元素執行完位置,再返回最終的結果。
比如有一個數組arr=[1,2,3,4,5],我們使用reduce來求和:
let sum = [1,2,3,4,5].reduce((a,b)=>a+b);
它巧妙的地方在於數組的每個元素都是函數,
callback返回一個復合函數作為previousValue,在reduce方法執行完之后,
也就返回了一個將整個數組中所有函數串式調用的一個函數。
6、然后是applyMiddleware.js
export default function applyMiddleware(...middlewares) {
//return一個函數,可以接收createStore方法作為參數
//給返回的store的dispatch方法再進行一次包裝
return createStore => (...args) => { 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) }
//傳入middlewareAPI參數並執行每一個外部函數,返回結果匯聚成數組
const chain = middlewares.map(middleware => middleware(middlewareAPI))
//用到了上面的compose方法
dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
官方的注釋中提到了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;
最終export了一個接受{ dispatch, getState }作為參數的function thunk,
這個thunk方法也就是傳給applyMiddleware方法的參數,
此時的middlewares只有thunk一個方法,
那么applyMiddleware中的chain也就很顯然的是執行了thunk方法后返回的結果,
我們再看redux-thunk的代碼,返回了一個接受next作為參數的方法!
applyMiddleware的下一行,
dispatch = compose(...chain)(store.dispatch),
chain只有一個function,所以這里可以忽略compose,
那么這一句就是將store.dispatch 作為next參數傳給了剛才的方法A,
終於,方法A返回了我們熟悉的dispatch方法。
但是注意,此時的dispatch方法還是原來的dispatch方法嗎?
它已經不是原來的它了。經過thunk方法的包裝,早已物是人非。
我們來看一下redux-thunk的代碼,第三行之后的4行,
如果dispatch方法接受的參數不是一個function,
那么這個dispatch就和普通的dispatch沒什么不同,
但如果此時的action是一個方法,那么就會執行此方法,且第一個參數是store.dispatch。
這意味着我們的action創建函數不再只能創建一個包含type的Object,而可以是一個方法。
你可能會問有什么用呢?當你在action中需要一個異步操作,並需要在回調中改變state的狀態的時候,這就是一個絕佳的解決方案。
所以說,applyMiddleware實際上做了一件事,就是根據外部函數(中間件函數)包裝原來的dispatch函數,然后將新的dispatch函數暴露出去。
再回頭去看createStore.jsx中的 return enhancer(createStore)(reducer, preloadedState)這句代碼,是不是明白了很多事情?
//很簡單卻很關鍵,我就不解釋了~ function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } /** * 將action與dispatch函數綁定,生成直接可以觸發action的函數, * 可以將第一個參數對象中所有的action都直接生成可以直接觸發dispatch的函數 * 而不需要一個一個的dispatch,生成后的方法對應原來action生成器的函數名 * */ export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } //actionCreators必須為object類型 if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] //給actionCreators的每一個成員都綁定dispatch方法生成新的方法, //然后注入新的對象中,新方法對應的key即為原來在actionCreators的名字 if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } else { warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`) } } return boundActionCreators 這個方法主要的作用就是將action與dispatch函數綁定,生成直接可以觸發action的函數。代碼比較簡單注釋也比較明白,就過去了~
8、bindActionCreators.js
//根據key和action生成錯誤信息 function getUndefinedStateErrorMessage(key, action) { //... } //一些警告級別的錯誤 function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) { const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' //判斷reducers是否為空數組 //判斷state是否是對象 //給state中存在而reducer中不存在的屬性添加緩存標識並警告 //... } //這個方法用於檢測用於組合的reducer是否是符合redux規定的reducer function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] //調用reducer方法,undefined為第一個參數 //使用前面說到過的ActionTypes.INIT和一個隨機type生成action作為第二個參數 //若返回的初始state為undefined,則這是一個不符合規定的reducer方法,拋出異常 //... }) } export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) //所有的鍵名 const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } //finalReducers是過濾后的reducers,它的每一個屬性都是一個function if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let sanityError //檢測每個reducer是否是符合標准的reducer try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } //如果不是成產環境,做一些警告判斷 if (process.env.NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} //下一個state樹 //遍歷所有reducers,然后將每個reducer返回的state組合起來生成一個大的狀態樹,所以任何action,redux都會遍歷所有的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) //如果此reducer返回的新的state是undefined,拋出異常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } //如果當前action對應的reducer方法執行完后,該處數據沒有變化,則返回原來的流程樹 return hasChanged ? nextState : state } }
9、Demo詳細解析

新建一個react項目

我項目大概長這個樣子

9.1先給自己建立一個Store庫,這就是你redux數據的倉庫了
store文件夾下有兩個文件,
reducers,把你各個頁面的reducer匯合起來,給他們起不同的好聽的名字,
我這里只有一個home頁面
import { combineReducers } from 'redux'
import home from 'pages/home/reducer'
export default combineReducers({
home
})
另一個文件是index.js
主要是用來創建你的庫,創建庫的時候我這里用到了兩個參數而且還引入了一個中間件
沒有中間件的Redux的過程是:action -> reducer,
而有了中間件的過程就是action -> middleware -> reducer,
使用中間件我們可以對action也就是對dispatch方法進行裝飾,
我們可以用它來實現異步action、打印日志、錯誤報告等功能。
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducers from './reducers' const store = createStore(reducers, applyMiddleware(thunk)) export default store
這時候你可以回頭去看看上面對redux-thunk源碼的解析,
你會發現這樣包裝后的dispatch非常可愛
當你在action中需要一個異步操作,並需要在回調中改變state的狀態的時候,這就是一個絕佳的解決方案。
9.2、給你的組件注入這個庫
在index.html里
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { Provider } from 'react-redux' import store from './store' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
我們可以參考源碼哦
//這里需要傳store所以我們使用Provider的時候把store傳入
//那么我們引入了Provider它為我們做了什么呢?
export function createProvider(storeKey = 'store') { const subscriptionKey = `${storeKey}Subscription` class Provider extends Component {
//將外部的store對象放入context對象中,使子孫組件上的connect可以直接訪問到context對象中的store。
getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } }
//constructor是Provider初始化時,用於獲取props的store對象
constructor(props, context) { super(props, context) this[storeKey] = props.store; }
//首先,它把它引入的內容全部變成它的子級元素,
//並且由於它處於整個index.html的最外層
//所以被它包裹的每一個元素都可以接收redux的store數據作為props
render() { return Children.only(this.props.children)
//this.props.children用於獲取當前組件的所有子組件
//children.only表示用於獲取僅有的一個子組件,沒有或者超過一個均會報錯.
//所以注意: 確保Provider組件的直接子級為單個封閉元素,切勿多個組件平行放置
//引申問題:當這個項目需要用到router時該怎么辦?把router包在倒數第二層,Provider在最外層 } } if (process.env.NODE_ENV !== 'production') { Provider.prototype.componentWillReceiveProps = function (nextProps) { if (this[storeKey] !== nextProps.store) { warnAboutReceivingStore() } } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, } return Provider } export default createProvider()
9.3、page下的home頁面有三個文件
actionTypes.js
export const GET_HOME_DATA = 'home/get_home_data'
你起什么名字都可以,只要不重復,你開心就好了。
這個名字貫穿了一條修改路線,你會發現接下來你的actionCreator.js和你的reducer.js里都用到了這個名字,不同的名字對應不同的數據操作,記號它,為了便於記號它,我為它專門設置了自己的actionType.js
actionCreator.js
import { GET_HOME_DATA } from './actionTypes'
export const loadHomeDataSync = (home) => {
return {
type: GET_HOME_DATA,
home
}
}
//先異步獲取數據,為了避免麻煩我這里用mock數據代替了
//再同步返回獲取到的數據
export const loadHomeDataAsync = (dispatch) => {
return () => {
fetch('/mock/data.json')
.then(response => response.json())
.then(result => {
dispatch(loadHomeDataSync(result.data))
})
}
}
reducer.js
//給你要用的數據設置初值,並且當新的數據來了以后,對數據做你想要的處理
//我這里是當原數據為空,state為新數據,原數據有的話,和新數據進行合並返回一個新的state
import { GET_HOME_DATA } from './actionTypes' const defaultState = { home:null } export default (state=defaultState, action) => { if (action.type === GET_HOME_DATA) { if(!!state.home){ return { home: [...state.home,...action.home] } } else{ return { ...state, home: [...action.home] } } } return state }
9.4 page下的view下的Home.js
import React, { Component } from 'react';
import { loadHomeDataAsync } from '../actionCreator'
//connect作用:連接React組件與 Redux store
import { connect } from 'react-redux'
const mapState = (state) => {
return {
home: state.home.home
}
}
const mapDispatch = (dispatch) => {
return {
loadCategories () {
dispatch(loadHomeDataAsync(dispatch))
}
}
}
class Home extends Component {
componentDidMount(){
this.props.loadCategories()
//在這里調用,當然,你想在哪調用都可以
}
render() {
console.log(this.props.home)
return (
<div>home</div>
);
}
}
export default connect(mapState,mapDispatch)(Home);
//記得在這里把他們connect起來
那么connect他究竟為我們做了什么呢?
為什么connect后面跟兩個括號?
它的基礎作用是:
a、從context里獲取store
b、在componentWillMount 里通過mapStateToProps獲取stateProp的值
c、在componentWillMount 里通過mapDispatchToProps獲取dispatchProps的值
d、在componentWillMount 里訂閱store的變化
e、將獲得的stateProp,dispatchProps,還有自身的props合成一個props傳給下面的組件
參考源碼:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } }
connect接收四個參數:mapStateToProps,mapDispatchToProps,mergeProps,optipons
返回:一個注入了 state 和 action creator 的 React 組件
mapStateToProps
傳入:state,ownProps
輸出:stateProps
mapDispatchToProps
這個非常關鍵,如果定義了這個參數,就會監聽redux store的變化,沒有的話,就不會。
該回調函數必須返回一個純對象,這個對象會與組件的 props 合並。
同時,如果指定了第二個ownProps,這個參數的值為傳入到組件的props,只要組件接受到新的props,mapStateToProps也會被調用
mergeProps(function)
stateProps,dispatchProps,自身的props將傳入到這個函數中。
默認是Object.assign({}, ownProps, stateProps, dispatchProps)
完整:
connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
結果
![]()
demo地址:
https://github.com/yangTwo100/reduxAsync_demo
以后再更。
