應用的全部狀態以一個對象的形式存儲在惟一的store裏面,咱們能夠把這個存儲狀態的對象叫狀態樹,而更改對象樹的惟一方式就是emit一個action(這個action是一個描述發生了什麼的對象),爲了把這個action轉換爲對象樹裏面的某個值,就須要提供一個reducer, 因此簡單來講,一個redux程序包括一個store、一個reducer和多個action。node
import { createStore } from 'redux'; export const reducer = (state = {}, action) => { switch (action.type) { case 'EXAMPLE_TEST': { // balabala return {text: 'ready go'} } default:{ return state; } } }; const initialState = {}; // 初始state const store = createStore(reducer, initialState); store.dispatch({type: 'EXAMPLE_TEST', data: {}}); store.subscribe(() => { const stateObj = store.getState(); if (stateObj.xxx...) { // state樹更改了, balabala } }) console.log(store.getState())
export default function createStore(reducer, preloadedState, enhancer) { ... dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
從參數能夠看出,它是action和reducer的粘合劑,調用一下dispatch生出一個初始化後的對象樹(如何生成,見下面的dispatch解釋),而後向外曝露出一些功能函數,具體功能函數見下面說明。react
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
把經過store.subscribe(()=>{})添加的listener放到nextListeners數據裏,並返回一個unsubscribe函數,用於取消訂閱,這裏面屢次出現的函數:ensureCanMutateNextListeners做用就是確保currentListeners和nextListeners不指向同一個數組,把將要執行的listeners和之後要執行的listeners區分開,這樣能夠避免掉listener正在被執行的時候,忽然取消訂閱的問題。webpack
function getState() { return currentState }
做用就是返回當前的狀態樹對象git
function observable() { const outerSubscribe = subscribe return { subscribe(observer) { if (typeof observer !== 'object') { throw new TypeError('Expected the observer to be an object.') } function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } }
把store針對state樹變動可observable,以支持redux和RxJS, XStream and Most.js等庫的聯合使用,須要再詳細說明的是$$observable,它來自於symbol-observable這個npm包,這個包的做用就是使一個對象observable, 而且避免掉observable對象調用完error, complete以後重複調用以及unsubscribe以後再調用next, error, complete函數,具體使用見README.md:https://github.com/benlesh/sy...github
function dispatch(action) { ...... // 省略掉了非關鍵代碼 try { isDispatching = true 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一個action,就是把當前state對象和action傳遞給傳給store的reducer做爲參數,而後reducer返回的新對象就是新的state樹,生成新的states樹以後,再執行全部經過store.subscribe函數註冊的listener, 註冊方式見上面subscribe說明,最後,咱們回到上面createStore遺留的dispatch({ type: ActionTypes.INIT })部分,就很明瞭了,就是生成初始的state樹。web
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) }
這個函數主要是替代creatStore傳進來的reducer, 使用場景如:代碼邏輯裏有代碼分割邏輯,須要動態加載reducer; 或者爲了實現redux的熱加載, 如:npm
if (module.hot) { module.hot.accept('../reducers', () => { const nextRootReducer = require('../reducers').default; store.replaceReducer(nextRootReducer); }); }
其中module.hot是webpack經過HotModuleReplacementPlugin開啓了模塊熱替換以後賦予module的hot屬性。編程
redux提供一個combineReducers工具,容許咱們寫多個小reducer,而後經過combineReducers組合成一個root reducer,也就是一個大reducer,源碼以下:redux
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 (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) ... return function combination(state = {}, action) { ... let hasChanged = false const nextState = {} 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) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
給combineReducers傳入一個reducer對象,而後返回一個reducer函數,參數裏面每一個value須要是一個小reducer函數, 這些小的reducer能夠分佈到不一樣的文件裏面,那麼觸類旁通,一些最小的reducer能夠經過combineReducers組裝成中等的reducer, 而後這些中等的reducer又能夠combineReducers成最大的reducer:api
export default combineReducers({ a: combineReducers(...), b: combineReducers(...), c: combineReducers(...) });
已經有redux-logger相似的工具了,咱們看它的定義:
// 默認的logger export const logger: Redux.Middleware; // 自定義logger export function createLogger(options?: ReduxLoggerOptions): Redux.Middleware; // Redux.Middleware export interface Middleware { <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>; }
從上面能夠看出返回類型都是Redux.Middleware,這代表它是以redux中間件的形式融入到redux中, 這些中間件實現方式都是統一的,接收一個MiddlewareAPI參數,而後返回一個function(這個function接收一個dispatch,而後將dispatch結果返回)。這些中間件注入到redux中須要藉助redux提供的applyMiddleware函數:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { const store = createStore(reducer, preloadedState, enhancer) let dispatch = store.dispatch let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware接收多箇中間件,而後返回一個函數,這個函數參數是一個createStore, 函數邏輯是調用createSotre拿到最基本的store,而後遍歷全部的中間件,給其傳入MiddlewareAPI,拿到中間件執行結果(返回是一個函數,是Redux.Middleware類型),而後經過compose把全部中間件給柯里化一下,把基本的store.dispatch武裝成一個強大的dispatch,這樣每次dispatch(action)就會走這些中間件了,也就能夠觀察action結果變化,其中compose的函數源代碼以下:
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))) }
結合中間件場景:dispatch = compose(...chain)(store.dispatch)能夠理解爲以傳入的中間件順序倒序爲基準,依次將基礎的store.dispatch傳給各個中間件的返回函數。
綜上,中間件落實到項目中使用就是:
import { createStore, applyMiddleware } from 'redux'; const bigCreateStore = applyMiddleware(middleware1, middleware2, ...)(createStore) bigCreateStore.dispatch(action)
還有不少其餘中間件,好比你action是個function的話就要藉助redux-thunk中間件。
咱們把它提取到工具類公共函數中,能夠是能夠,可是整個項目代碼看起來不優雅,咱們能夠向redux看齊,以一箇中間件的形式落地這個異步請求處理,咱們能夠本身實現一箇中間件,也可使用現成的, 好比:https://github.com/agraboso/r...
整個項目中處處充滿着subscribe,並且一旦整個對象樹更改了,全部組件的subscribe都要執行state變化判斷,看是否從新render,這樣容易作成無用功,咱們能夠藉助已有的工具:react-redux, 給咱們解決掉這個判斷處理麻煩,使用事例:
import { Provider, connect } from 'react-redux'; class App extends Component { render () { return ( <div>app內容</div> ); } } export default export default class RootComponent extends Component { render () { return ( <Provider store={store}> connect(state => ({ a0: state.a.a0, b0: state.b.b0, c0: state.c.c0 }), { ...actions })(App) </Provider> ) } }
從上面使用上看,主要是Provider Class和connect函數兩個東西。經過connect包裝以後的Component和Provider經過context進行通訊,分開來看,先說Provider:
Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, }
其源碼中能夠看出它接收兩個prop,一個是store, 一個是children。另外子孫組件想要和Provider通訊,使用store和subscriptionKey能力,就得經過context[storeKey]和context[subscriptionKey]使用,同時子孫組件也要提供可使用context對象的依據(好比能夠看connect函數源代碼裏面的高階組件):
const contextTypes = { [storeKey]: storeShape, [subscriptionKey]: subscriptionShape, }
再看connect:
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的結果就是返回一個高階組件,這個高階組件中的兩個重要函數就是:
initSelector() { const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) this.selector = makeSelectorStateful(sourceSelector, this.store) this.selector.run(this.props) } initSubscription() { if (!shouldHandleStateChanges) return const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) }
initSelector判斷state狀態樹或者actions是否更改,來決定被包裹組件的新props,關鍵代碼見:
const nextProps = sourceSelector(store.getState(), props) if (nextProps !== selector.props || selector.error) { selector.shouldComponentUpdate = true selector.props = nextProps selector.error = null } ... render() { ... return createElement(WrappedComponent, this.addExtraProps(selector.props)) ... }
initSubscription的主要做用就是監聽store的state對象樹是否變化,若是變化,就執行以下代碼:
onStateChange() { this.selector.run(this.props) // 獲取最新的props if (!this.selector.shouldComponentUpdate) { this.notifyNestedSubs() } else { this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate this.setState(dummyState) } }
也就是說先獲取最新的props, 這個props裏面包括states,而後判斷是否須要從新渲染,若是須要,則觸發setState,引發高階組件react生命週期的執行,再看this.notifyNestedSubs()這句,若是被包裹組件訂閱了state變化,那麼會依次執行全部的listener,被包裹組件若是若是想訂閱,須要藉助context,由於高階組件裏面定義了以下代碼:
const childContextTypes = { [subscriptionKey]: subscriptionShape, }
以上就是問題四的相關內容,使用過程當中須要注意的是connect函數的第二個參數也就是mapDispatchToProps能夠是個actions對象,也能夠是一個方法,我習慣用對象,寫起來方便,其實底層最終都是同樣的,只是若是用的是對象的話,react-redux內部會調用
bindActionCreators(mapDispatchToProps, dispatch)
把對象處理成新對象,這個新對象的values都是
(...args) => dispatch(actionCreator(...args))
因此就能夠知足咱們項目中this.props.xxx(data)來dispatch一個action。
高階組件的props來源兩部分,分別是state對橡樹裏面的對象和actions, 詳細看react-redux包下面的src/connect/selectorFactory.js代碼片斷
function handleSubsequentCalls(nextState, nextOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual(nextState, state) state = nextState ownProps = nextOwnProps if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() return mergedProps }
redux源代碼處處充滿着函數式編程以及調用compose柯里化,源代碼量很少,簡短乾淨,看了很舒服,上面的整個分析一句的redux版本是3.7.2,react-redux版本5.1.1, 若是後續版本相關邏輯更改了請見諒。