一、首先讓咱們看看都有哪些內容javascript
二、讓咱們看看redux的流程圖html
Store:一個庫,保存數據的地方,整個項目只有一個java
建立storereact
Redux提供 creatStore 函數來生成 Storegit
// 引入redux import { createStore } from 'redux'; //建立Store 須要傳遞一個函數fn 這裏的fn是以後會說起的reducers const store = createStore(fn);
State:狀態,某時刻的數據便是Store的狀態github
獲取狀態的方法是store.getState()json
Action:行爲,它有一個不可或缺的type屬性
redux
action還能夠攜帶其餘內容api
咱們可使用action來改變State的值,數組
從而將咱們須要的數據經過Action「運輸」到 Store;
dispatch:發送action
dispatch(action)接受一個action對象爲參數,並將它發送出去,
Store接受Action,接受以後須要返回一個新的State(狀態)
Reducer:處理器
dispatch(action)接受一個action對象爲參數,並將它發送出去,
Store接受Action,接受以後須要返回一個新的State(狀態)
而建立這個新的狀態的過程就是reducer
三、從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 是普通對象.
四、接下來是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 }
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 } } }
五、接下來就是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
第一個函數有四個參數
若是有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方法執行完以後,
也就返回了一個將整個數組中全部函數串式調用的一個函數。
六、而後是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 } } }
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的函數。代碼比較簡單註釋也比較明白,就過去了~
八、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 } }
九、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也就是對dispatch方法進行裝飾,
咱們能夠用它來實現異步action、打印日誌、錯誤報告等功能。
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducers from './reducers' const store = createStore(reducers, applyMiddleware(thunk)) export default storeaction -> reduceraction -> middleware -> reducer
這時候你能夠回頭去看看上面對redux-thunk源碼的解析,
你會發現這樣包裝後的dispatch很是可愛
當你在action中須要一個異步操做,並須要在回調中改變state的狀態的時候,這就是一個絕佳的解決方案。
9.二、給你的組件注入這個庫
在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.三、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 組件
傳入:state,ownProps
輸出:stateProps
這個很是關鍵,若是定義了這個參數,就會監聽redux store的變化,沒有的話,就不會。
該回調函數必須返回一個純對象,這個對象會與組件的 props 合併。
同時,若是指定了第二個ownProps,這個參數的值爲傳入到組件的props,只要組件接受到新的props,mapStateToProps也會被調用
stateProps,dispatchProps,自身的props將傳入到這個函數中。
默認是Object.assign({}, ownProps, stateProps, dispatchProps)
完整:
connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
結果
demo地址:
https://github.com/yangTwo100/reduxAsync_demo
之後再更。