Redux不止是使用

Redux的設計思想很簡單,一、web應用是一個狀態機,視圖與狀態一一對應;二、全部狀態保存在一個對象裏
複製代碼

基本概念和API

  1. Store 應用狀態的管理者,能夠看做一個數據庫,包含如下函數web

    const store = {
            dispatch(action),               // 觸發state改變的惟一方法
            subscribe(listener),            // 訂閱函數,store變化觸發的訂閱的監聽函數,返回取消訂閱的方法
            getState(),                     // 獲取state的方法
            replaceReducer(nextReducer),    // 替換reducer
            [$$observable]: observable
          }
    複製代碼

    一個應用只應有一個單一的 store,其管理着惟一的應用狀態 state Redux提供createStore函數,用於生成store數據庫

    import { createStore } from 'redux';
    const store = createStore(reducer);
    複製代碼
  2. statejson

    一個對象,包含全部數據,能夠看做數據庫中的數據,經過const state = store.getState()獲取redux

  3. Actionapi

    一個包含type屬性的對象,做用是描述如何修改state,它會運輸數據到store,做爲reducer函數的參數數組

  4. Action Creatorbash

    生成Action的函數app

  5. dispatch異步

    view發出Action的惟一方法,redux規定不能直接在業務代碼中修改state,若想要修改,只能經過store.dispatch(Action)實現async

  6. Reducer

    一個純函數,會根據Acion的type值生成新的state替換調原來調state

源碼解析

只對關鍵代碼進行解讀。

  1. 目錄結構

  1. index.ts

    先看入口文件

    import createStore from './createStore'
    import combineReducers from './combineReducers'
    import bindActionCreators from './bindActionCreators'
    import applyMiddleware from './applyMiddleware'
    import compose from './compose'
    import warning from './utils/warning'
    import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
    
    //...
    
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose,
      __DO_NOT_USE__ActionTypes
    }
    複製代碼

    很簡單,導出一個對象,接下來一個個分析

  2. createStore.ts

    import $$observable from './utils/symbol-observable'
        
        import {
          Store,
          PreloadedState,
          StoreEnhancer,
          Dispatch,
          Observer,
          ExtendState
        } from './types/store'
        import { Action } from './types/actions'
        import { Reducer } from './types/reducers'
        import ActionTypes from './utils/actionTypes'
        import isPlainObject from './utils/isPlainObject'
        
    
        export default function createStore (
          reducer,   // 由於dispatch會自動觸發reducer的執行,因此在生成store的時候須要把reducer函數傳遞進來
          preloadedState?,  // 初始state
          enhancer?  // 加強,經過一系列中間件加強dispatch函數,例如對於一步Action對處理,後面詳細介紹
        ) {
            // ...對參數的判斷
            
            if (typeof enhancer !== 'undefined') {
                if (typeof enhancer !== 'function') {
                  throw new Error('Expected the enhancer to be a function.')
                }
                return enhancer(createStore)(
                  reducer,
                  preloadedState as PreloadedState<S>
                ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
              }
        
            let currentReducer = reducer                      // reducer函數
            let currentState = preloadedState                 // 當前state
            let currentListeners = []  // 用於存儲訂閱的回調函數,dispatch 後逐個執行
            let nextListeners = currentListeners              // 下一個事件循環的回調函數數組
            let isDispatching = false                         // 是否正在dispatch
            
            // 判斷當前的回調函數隊列是否與下一個回調函數隊列相同,不能影響本次的執行
            function ensureCanMutateNextListeners() {
                if (nextListeners === currentListeners) {
                  nextListeners = currentListeners.slice()
                }
            }
        
    
            // 獲取state
            function getState() {
              // 直接返回state
              return currentState
            }
          
            // 訂閱--取消訂閱
            function subscribe(listener) {
        
                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)
                    currentListeners = null
                }
            }
        
            // 派發一個Action用於改變state
            // 若是dispatch的不是一個對象類型的action(同步),而是一個Promise/Thunk(異步),
            // 就須要引入redux-thunk 等中間件來反轉控制權,具體會在applyMiddlewares()方法中解析
            function dispatch(action) {
                // 確保是一個普通的對象,若非普通對象,原型鏈上會出現其餘屬性
                if (!isPlainObject(action)) {
                    throw new Error(
                        'Actions must be plain objects. ' +
                        'Use custom middleware for async actions.'
                    )
                }
        
                // 確保擁有type屬性
                if (typeof action.type === 'undefined') {
                    throw new Error(
                        'Actions may not have an undefined "type" property. ' +
                        'Have you misspelled a constant?'
                    )
                }
        
                // 是否正在dispatch
                if (isDispatching) {
                    throw new Error('Reducers may not dispatch actions.')
                }
        
                try {
                    isDispatching = true
                    // dispatch 觸發reducer函數,返回state,賦值給currentState,這也是爲何咱們要在建立store對象的時候傳入reducer
                    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
            }
        
            // 替換當前的reducer
            function replaceReducer (
                nextReducer
            ) {
        
                // 直接替換,簡單粗暴
                currentReducer = nextReducer
            
                dispatch({ type: ActionTypes.REPLACE })
                return store
            }
        
            // 這是留給 可觀察/響應式庫 的接口,具體實現能夠自行搜索一下
            function observable() {
                const outerSubscribe = subscribe
                return {
              
                    subscribe(observer) {
                        if (typeof observer !== 'object' || observer === null) {
                            throw new TypeError('Expected the observer to be an object.')
                        }
        
                        function observeState() {
                        const observerAsObserver = observer
                        if (observerAsObserver.next) {
                            observerAsObserver.next(getState())
                        }
                    }
        
                    observeState()
                    const unsubscribe = outerSubscribe(observeState)
                    return { unsubscribe }
                },
        
                [$$observable]() {
                    return this
                }
            }
          }
        
          // 初始化state
          dispatch({ type: ActionTypes.INIT })
        
          // 返回store對象
          const store = {
            dispatch,
            subscribe,
            getState,
            replaceReducer,
            [$$observable]: observable
          }
          return store
        }
    
    複製代碼

    createStore(reducer, preloadState?, enhancer?)是Redux的核心代碼,它給咱們提供了獲取state的方法getState()、觸發state改變的方法dispatch(Action)等

  3. combineReducers.ts

import { Reducer } from './types/reducers'
    import { AnyAction, Action } from './types/actions'
    import ActionTypes from './utils/actionTypes'
    import warning from './utils/warning'
    import isPlainObject from './utils/isPlainObject'
    import {
      ReducersMapObject,
      StateFromReducersMapObject,
      ActionFromReducersMapObject
    } from './types/reducers'
    import { CombinedState } from './types/store'
    
    export default function combineReducers(reducers) {
      const reducerKeys = Object.keys(reducers)
      const finalReducers: ReducersMapObject = {}
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
      }
      // 全部reducer的key集合
      const finalReducerKeys = Object.keys(finalReducers)

      let unexpectedKeyCache
      
    // 返回合成後的 reducer
    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]                        // 獲取當前子的state
          const nextStateForKey = reducer(previousStateForKey, action)  // 執行起對應的reducer
          nextState[key] = nextStateForKey                              // 將子nextState掛載到對應的鍵名
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state                           // 返回總的state
      }
    }

複製代碼

假如咱們的應用有兩個狀態須要管理,一個是打印日誌,另外一個是計數器功能,咱們能夠把它們寫在一個reducer中,但若是還有其餘動做,這個堆在一個會讓代碼變得難以維護,因此最好但辦法是拆分reducer,使每個動做變得單一,最後在整合在一塊兒,combineReducers()就是爲此而生的。須要注意的是,子reducer的名稱應該與起對應的state相同。

好比咱們的目錄結構以下

reducers/
   ├── index.js
   ├── counterReducer.js
   ├── todosReducer.js
複製代碼

就能夠這樣實現

/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'

const rootReducer = combineReducers({
  counter: counterReducer, // 鍵名就是該 reducer 對應管理的 state
  todos: todosReducer
})

export default rootReducer

-------------------------------------------------

/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 傳入的 state 實際上是 state.counter
  switch (action.type) {
    case 'INCREMENT':
      return counter + 1 // counter 是值傳遞,所以能夠直接返回一個值
    default:
      return counter
  }
}

-------------------------------------------------

/* reducers/todosReducers */
export default function todosReducer(todos = [], action) { // 傳入的 state 實際上是 state.todos
  switch (action.type) {
    case 'ADD_TODO':
      return [ ...todos, action.payload ]
    default:
      return todos
  }
}
複製代碼

這樣作的好處不言而喻。不管您的應用狀態樹有多麼的複雜,均可以經過逐層下分管理對應部分的 state:

counterReducer(counter, action) -------------------- counter
                              ↗                                                              ↘
rootReducer(state, action) —→∑     ↗ optTimeReducer(optTime, action) ------ optTime ↘         nextState
                              ↘—→∑                                                    todo  ↗
                                   ↘ todoListReducer(todoList,action) ----- todoList ↗


注:左側表示 dispatch 分發流,∑ 表示 combineReducers;右側表示各實體 reducer 的返回值,最後彙總整合成 nextState
複製代碼
  1. bindActionCreator.ts
import { Dispatch } from './types/store'
    import {
      AnyAction,
      ActionCreator,
      ActionCreatorsMapObject
    } from './types/actions'
    
    function bindActionCreator<A extends AnyAction = AnyAction>(
      actionCreator: ActionCreator<A>,
      dispatch: Dispatch
    ) {
      return function (this: any, ...args: any[]) {
       // 給Action Creator 加上dispatch技能,也就是包裹一下
        return dispatch(actionCreator.apply(this, args))
      }
    }

    
    export default function bindActionCreators(
      actionCreators,
      dispatch
    ) {
        // 處理actionCreators是函數的狀況
        if (typeof actionCreators === 'function') {
            return bindActionCreator(actionCreators, dispatch)
        }
    
        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"?`
            )
        }
        
        // 處理actionCreators是對象的狀況
        const boundActionCreators = {}
        for (const key in actionCreators) {
            const actionCreator = actionCreators[key]
            if (typeof actionCreator === 'function') {
              boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
            }
        }
        return boundActionCreators
    }
複製代碼

bindActionCreators(actionCreators, dispatch)這個函數的功能也比較單一,就是dispatch(ActionCreator(XXX)),這個方法在異步狀況下,沒什麼卵用。。。

  1. compose.ts
export default function compose(...funcs) => 

// 複合函數 compose(func1, func2, func3)(0) => func3(func2(func1(0)))
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: any) => a(b(...args)))
}

複製代碼

這個函數很是簡單,功能也很單一,主要用了數組的reduce方法,將函數做爲reduce的回調函數的參數,參考文檔

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
複製代碼
  1. applyMiddlewares.ts

中間件就是一個函數,對dispatch進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其餘功能。

它是 Redux 的原生方法,做用是將全部中間件組成一個數組,依次執行。目的是爲了在dispatch先後,統一處理想作對事。

import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'

export default function applyMiddleware(
  ...middlewares
) {
  return (createStore) => (
    reducer,
    ...args
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch = () => {
    }
    // 中間件增長api
    const middlewareAPI = {
      getState,
      dispatch
    }
    // chain存放中間件的數組,逐個增長api
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose複合函數 a(b(c(0))) => compose(a, b, c)(0)
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼

從源碼能夠看出,這個函數的主要做用就是經過各類中間件對dispatch進行加強,藉此咱們就能夠處理異步Action等問題了。

咱們能夠思考一下爲何處理異步Action就須要對dispatch進行加強,緣由以下:

(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其餘功能,也承擔不了,由於理論上,純函數不能進行讀寫操做。

(2)View:與 State 一一對應,能夠看做 State 的視覺層,也不合適承擔其餘功能。

(3)Action:存放數據的對象,即消息的載體,只能被別人操做,本身不能進行任何操做。

因此只有發送 Action 的這個步驟,即store.dispatch()方法,能夠添加功能。

異步操做的基本思路是什麼呢?

同步操做只須要發出一種Action,異步操做則須要發出3種,發送時、成功時、失敗時,例如:

{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
複製代碼

一樣的state也須要進行對應的改造

let state = {
    // ... 
    isFetching: true,       // 是否正在抓去數據
    didInvalidate: true,    // 數據是否過期
    lastUpdated: 'xxxxxxx'  // 更新數據時間
}
複製代碼

因此異步操做的思路應該是:

  • 操做開始時,送出一個 Action,觸發 State 更新爲"正在操做"狀態,View 從新渲染
  • 操做結束後,再送出一個 Action,觸發 State 更新爲"操做結束"狀態,View 再一次從新渲染

Redux-Thunk

前面說處處理異步Action須要藉助到redux-thunk等中間件,如今具體分析一下redux-thunk是怎麼處理異步請求的。

異步操做至少要送出兩個 Action:用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;如何才能在操做結束時,系統自動送出第二個 Action 呢?

最主要的就是在Action Creator中處理,例如store.dispatch(fetchPosts(selectedPost))

fetchPosts()就是一個Action Creator,看一下內部實現:

const fetchPosts = postTitle => (dispatch, getState) => {
  // 先發送第一個Action,同步的 
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => 
        // 請求到結果後,發送第二個Action,異步
        dispatch(receivePosts(postTitle, json)
    ));
  };
};
複製代碼

值得注意的是,Action Creator返回的是一個函數,而正常返回應該是一個對象,而且這個函數的參數是dispatch和getState兩個方法。

這個咱們就須要用到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;
複製代碼

竟是如此簡單的函數,這裏咱們結合applyMiddleware分析一下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const store = createStore(
  reducer,
  {},
  applyMiddleware(thunk)
);
複製代碼

applyMiddleware函數的參數是thunk函數 ==>

({ dispatch, getState }) => (next) => (action) => {
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }
        return next(action);
    }
複製代碼

回顧一下applyMiddleware函數 ==>

import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'

export default function applyMiddleware(
  ...middlewares
) {
  return (createStore) => (
    reducer,
    ...args
  ) => {
    const store = createStore(reducer, ...args)
    let dispatch = () => {
    }
    // 中間件增長api
    const middlewareAPI = {
      getState,
      dispatch
    }
    // chain存放中間件的數組,逐個增長api
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose複合函數 a(b(c(0))) => compose(a, b, c)(0)
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼

應用了redux-thunk,dispatch函數就會變成

dispatch = (store.dispatch) => (action) => {
        // 處理action是函數的狀況
        if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
        }
        
        // 處理action是對象的狀況
        return store.dispatch(action)
    }
複製代碼

總結了這些,若有不對的地方還望你們指正,但願有興趣的朋友一塊兒交流交流。

相關文章
相關標籤/搜索