自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redux源碼解析系列 (二)牛鼻的createStore

開發(fā) 后端
本篇文章,我們來解析createStore

 

前言
在上一章了解了【Redux 源碼解析系列(一) -- Redux的實現(xiàn)思想】之后,我們正式進(jìn)入源碼解析~

Redux 其實是用來幫我們管理狀態(tài)的一個框架,它暴露給我們四個接口,分別是:

  • createStore
  • combineReducers
  • bindActionCreators
  • applyMiddleware
  • compose

本篇文章,我們來解析createStore
下面我來對其進(jìn)行解析~

INIT
這個方法是redux保留用的,用來初始化reducer的狀態(tài) 

  1. export const ActionTypes = { 
  2.   INIT: '@@redux/INIT' 

前面說 createStore的作用就是:創(chuàng)建一個store來管理app的狀態(tài),唯一改變狀態(tài)的方式就是dispatch一個action,最終返回一個object。

  1. return { 
  2.     dispatch, 
  3.     subscribe, 
  4.     getState, 
  5.     replaceReducer, 
  6.     [$$observable]: observable 

不過replaceReducer,跟[$$observable]:都不常用~ ,所以這里只對前三的接口做解析。

createStore
在一個app里,只能有一個store,如果你想指明不同的state對應(yīng)不同的action,可以用combineReducers去合并不同的reducer。

參數(shù):

  • reducer(function):就是通過傳入當(dāng)前State,還有action,計算出下一個state,返回回來。
  • preloadedState(any):initial state
  • enhancer(function):增強store的功能,讓它擁有第三方的功能,比如middleware.Redux里面唯一的enhancer就是applyMiddleware()
  1. export default function createStore(reducer, preloadedState, enhancer) { 
  2. // 第一段說的就是當(dāng)?shù)诙€參數(shù)沒有傳preloadedState,而直接傳function的話,就會直接把這個function當(dāng)成enhancer 
  3.   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { 
  4.     enhancer = preloadedState 
  5.     preloadedState = undefined 
  6.   } 
  7.   // 當(dāng)?shù)谌齻€參數(shù)傳了但是不是function也會報錯 
  8.   if (typeof enhancer !== 'undefined') { 
  9.     if (typeof enhancer !== 'function') { 
  10.       throw new Error('Expected the enhancer to be a function.'
  11.     } 
  12.     //關(guān)鍵的一個就在于這里了,在前一篇講applyMiddleware的時候介紹了這么做的意義, 
  13.     //實際就是把createStore這件事在applyMiddleware里面做,轉(zhuǎn)移了鍋。 
  14.     return enhancer(createStore)(reducer, preloadedState) 
  15.   } 
  16.  
  17.   if (typeof reducer !== 'function') { 
  18.     throw new Error('Expected the reducer to be a function.'
  19.   } 
  20.  
  21.   let currentReducer = reducer 
  22.   let currentState = preloadedState 
  23.   let currentListeners = [] 
  24.   let nextListeners = currentListeners 
  25.   let isDispatching = false 

上面是第一個part,在校驗完參數(shù)的正確之后,終于可以干點正事兒了。createStore最終會返回一個Object.

  1.  dispatch, 
  2.  subscribe, 
  3.  getState 

接下來看看里面都做了什么:

getState
getState作用就是將當(dāng)前state的狀態(tài)返回回來,沒啥好說的~

  1. function getState() { 
  2.    return currentState 

subscribe
作用:添加監(jiān)聽函數(shù)listener,它會在每次dispatch action的時候調(diào)用。

參數(shù):listener(function): 在每一次dispatch action的時候都會調(diào)用的函數(shù)

返回:返回一個移除listener的函數(shù)

  1. // 這個函數(shù)的作用就是,如果發(fā)現(xiàn)nextListeners,nextListeners指向同一個堆棧的話,就淺復(fù)制一份,這樣改nextListeners就不會改到currentListeners 
  2. function ensureCanMutateNextListeners() { 
  3.     if (nextListeners === nextListeners) { 
  4.       nextListeners = currentListeners.slice() 
  5.     } 
  6.  
  7. function subscribe(listener) { 
  8.     if (typeof listener !== 'function') { 
  9.       throw new Error('Expected listener to be a function.'
  10.     } 
  11.  
  12.     let isSubscribed = true 
  13.  
  14.     ensureCanMutateNextListeners() 
  15.     // 直接將監(jiān)聽的函數(shù)放進(jìn)nextListeners里 
  16.     nextListeners.push(listener) 
  17.  
  18.     return function unsubscribe() { 
  19.     // 如果已經(jīng)移除了就直接返回 
  20.       if (!isSubscribed) { 
  21.         return 
  22.       } 
  23.  
  24.       isSubscribed = false 
  25.  
  26.       ensureCanMutateNextListeners() 
  27.       // 沒有移除的話,先找到位置,通過splice移除 
  28.       const index = nextListeners.indexOf(listener) 
  29.       nextListeners.splice(index, 1) 
  30.     } 
  31.   } 

在使用的時候就可以:

  1. const unsubscribe = store.subscribe(() => 
  2.   console.log(store.getState()) 
  3.  
  4. unsubscribe() 
  5. dispatch 

dispatch
dispatch 作為一個重點函數(shù)~ 其實它的作用就是觸發(fā)狀態(tài)的改變。

參數(shù):action(object),它是一個描述發(fā)生了什么的對象,其中type是必須的屬性。

返回:這個傳入的object

  1. function dispatch(action) { 
  2.     if (!isPlainObject(action)) { 
  3.       throw new Error( 
  4.         'Actions must be plain objects. ' + 
  5.         'Use custom middleware for async actions.' 
  6.       ) 
  7.     } 
  8.     // 
  9.     if (typeof action.type === 'undefined') { 
  10.       throw new Error( 
  11.         'Actions may not have an undefined "type" property. ' + 
  12.         'Have you misspelled a constant?' 
  13.       ) 
  14.     } 
  15.     // 防止多次dispatch請求同時改狀態(tài),一定是前面的dispatch結(jié)束之后,才dispatch下一個 
  16.     if (isDispatching) { 
  17.       throw new Error('Reducers may not dispatch actions.'
  18.     } 
  19.  
  20.     try { 
  21.       isDispatching = true 
  22.       currentState = currentReducer(currentState, action
  23.     } finally { 
  24.       isDispatching = false 
  25.     } 
  26.     // 在dispatch的時候,又將nextListeners 賦值回currentListeners, 
  27.     const listeners = currentListeners = nextListeners 
  28.     for (let i = 0; i < listeners.length; i++) { 
  29.       const listener = listeners[i] 
  30.       listener() 
  31.     } 
  32.  
  33.     return action 
  34.   } 

在上面一系列完成之后,需要初始化appState的狀態(tài)。當(dāng)INIT action被dispatched 的時候,每個reducer都會return回它的初始狀態(tài)。

  1. dispatch({ type: ActionTypes.INIT }) 

自問自答環(huán)節(jié)
為什么createStore中既存在currentListeners也存在nextListeners?
在上面的源碼中,createStore函數(shù)為了保存store的訂閱者,不僅保存了當(dāng)前的訂閱者currentListeners而且也保存了nextListeners。createStore中有一個內(nèi)部函數(shù)ensureCanMutateNextListeners:

  1. function ensureCanMutateNextListeners() { 
  2.     if (nextListeners === currentListeners) { 
  3.       nextListeners = currentListeners.slice() 
  4.     } 

這個函數(shù)實質(zhì)的作用是確??梢愿淖僴extListeners,如果nextListeners與currentListeners一致的話,將currentListeners做一個拷貝賦值給nextListeners,然后所有的操作都會集中在nextListeners,比如我們看訂閱的函數(shù)subscribe:

  1. function subscribe(listener) { 
  2. // ...... 
  3.     let isSubscribed = true 
  4.  
  5.     ensureCanMutateNextListeners() 
  6.     nextListeners.push(listener) 
  7.  
  8.     return function unsubscribe() { 
  9.         // ...... 
  10.         ensureCanMutateNextListeners() 
  11.         const index = nextListeners.indexOf(listener) 
  12.         nextListeners.splice(index, 1) 

我們發(fā)現(xiàn)訂閱和解除訂閱都是在nextListeners做的操作,然后每次dispatch一個action都會做如下的操作:

  1. function dispatch(action) { 
  2.     try { 
  3.       isDispatching = true 
  4.       currentState = currentReducer(currentState, action
  5.     } finally { 
  6.       isDispatching = false 
  7.     } 
  8.     // 相當(dāng)于currentListeners = nextListeners const listeners = currentListeners 
  9.     const listeners = currentListeners = nextListeners 
  10.     for (let i = 0; i < listeners.length; i++) { 
  11.       const listener = listeners[i] 
  12.       listener() 
  13.     } 
  14.     return action 
  15.   } 

我們發(fā)現(xiàn)在dispatch中做了const listeners = currentListeners = nextListeners,相當(dāng)于更新了當(dāng)前currentListeners為nextListeners,然后通知訂閱者,到這里我們不禁要問為什么要存在這個nextListeners?     其實代碼中的注釋也是做了相關(guān)的解釋:

The subscriptions are snapshotted just before every dispatch() call.If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress.However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list.

來讓我這個六級沒過的渣渣翻譯一下: 訂閱者(subscriptions)在每次dispatch()調(diào)用之前都是一份快照(snapshotted)。如果你在listener被調(diào)用期間,進(jìn)行訂閱或者退訂,在本次的dispatch()過程中是不會生效的,然而在下一次的dispatch()調(diào)用中,無論dispatch是否是嵌套調(diào)用的,都將使用最近一次的快照訂閱者列表。用圖表示的效果如下:

我們從這個圖中可以看見,如果不存在這個nextListeners這份快照的話,因為dispatch導(dǎo)致的store的改變,從而進(jìn)一步通知訂閱者,如果在通知訂閱者的過程中發(fā)生了其他的訂閱(subscribe)和退訂(unsubscribe),那肯定會發(fā)生錯誤或者不確定性。例如:比如在通知訂閱的過程中,如果發(fā)生了退訂,那就既有可能成功退訂(在通知之前就執(zhí)行了nextListeners.splice(index, 1))或者沒有成功退訂(在已經(jīng)通知了之后才執(zhí)行了nextListeners.splice(index, 1)),這當(dāng)然是不行的。因為nextListeners的存在所以通知訂閱者的行為是明確的,訂閱和退訂是不會影響到本次訂閱者通知的過程。

還是看不懂是什么意思?????一個簡單粗俗的例子:

當(dāng)在執(zhí)行這段代碼到第三個listener的時候:

  1. for (let i = 0; i < listeners.length; i++) { 
  2.   const listener = listeners[i] 
  3.   listener() 

你突然把第2個listener給splice了。這樣的話此時上面的循環(huán)本來是執(zhí)行完第三個要執(zhí)行第四個了,但是由于數(shù)組中的第2個listener被splice掉了,所以數(shù)組后面的元素都要往前移動一個位置,這時數(shù)組的第四個listener就移動到原先第三個的位置了,數(shù)組的第五個listener就移動到原先第四個的位置了,因此循環(huán)本要執(zhí)行第四個的,結(jié)果由于第四個往前移動了,實際執(zhí)行的是原先的第五個,所以導(dǎo)致原先的第四個沒有被執(zhí)行。。

沒錯,上面講的就是這樣的?。。?!哈哈哈明白了吧?。。。?/p>

但是這里又有一個問題了:

JavaScript不是單線程的嗎?為啥在執(zhí)行循環(huán)的時候,會執(zhí)行unsubscribe()操作
百思不得其解的情況下,去Redux項目下開了一個issue,得到了維護(hù)者的回答:

得了,我們再來看看測試相關(guān)的代碼吧??赐曛笪伊私獾搅恕5拇_,因為JavaScript是單線程語言,不可能出現(xiàn)出現(xiàn)想上述所說的多線程場景,但是我忽略了一點,執(zhí)行訂閱者函數(shù)時,在這個回調(diào)函數(shù)中可以執(zhí)行退訂或者訂閱事件。例如:

  1. const store = createStore(reducers.todos) 
  2. const unsubscribe1 = store.subscribe(() => { 
  3.     const unsubscribe2 = store.subscribe(()=>{}) 
  4. }) 

這不就實現(xiàn)了在通知listener的過程中混入訂閱subscribe與退訂unsubscribe嗎?

為什么Reducer中不能進(jìn)行dispatch操作?
我們知道在reducer函數(shù)中是不能執(zhí)行dispatch操作的。一方面,reducer作為計算下一次state的純函數(shù)是不應(yīng)該承擔(dān)執(zhí)行dispatch這樣的操作。另一方面,即使你嘗試著在reducer中執(zhí)行dispatch,也并不會成功,并且會得到"Reducers may not dispatch actions."的提示。因為在dispatch函數(shù)就做了相關(guān)的限制:

  1. function dispatch(action) { 
  2.     if (isDispatching) { 
  3.       throw new Error('Reducers may not dispatch actions.'
  4.     } 
  5.     try { 
  6.       isDispatching = true 
  7.       currentState = currentReducer(currentState, action
  8.     } finally { 
  9.       isDispatching = false 
  10.     } 
  11.  
  12.     //...notice listener 

在執(zhí)行dispatch時就會將標(biāo)志位isDispatching置為true。然后如果在currentReducer(currentState, action)執(zhí)行的過程中由執(zhí)行了dispatch,那么就會拋出錯誤('Reducers may not dispatch actions.')。之所以做如此的限制,是因為在dispatch中會引起reducer的執(zhí)行,如果此時reducer中又執(zhí)行了dispatch,這樣就落入了一個死循環(huán),所以就要避免reducer中執(zhí)行dispatch。

參考文獻(xiàn):

  1. https://github.com/MrErHu/blog/issues/18
  2. https://zhuanlan.zhihu.com/p/57316118

 【編輯推薦】

 

責(zé)任編輯:姜華 來源: 前端陽光
相關(guān)推薦

2016-09-22 15:50:38

JavascriptRedux源碼解析

2012-02-02 15:56:48

Android 4.0Launcher源碼分析

2021-07-09 06:48:30

注冊源碼解析

2021-07-03 08:51:30

源碼Netty選擇器

2023-07-19 14:00:50

OverlayC語言

2021-11-08 15:06:15

鴻蒙HarmonyOS應(yīng)用

2023-04-12 15:09:25

Overlay fs鴻蒙

2015-09-16 09:10:27

Java源碼解析

2022-05-20 10:32:49

事件循環(huán)器事件隊列鴻蒙

2015-08-18 08:55:03

redux核心

2021-06-17 09:36:07

鴻蒙HarmonyOS應(yīng)用

2019-04-28 16:10:50

設(shè)計Redux前端

2018-07-19 15:57:46

ViewStub源碼方法

2010-11-08 09:04:31

IBMPower

2021-02-20 06:09:46

libtask協(xié)程鎖機(jī)制

2020-12-01 15:00:20

Java 基礎(chǔ)

2012-11-06 11:07:59

jQueryJSjQuery框架

2024-11-18 16:15:00

2024-01-18 08:31:22

go實現(xiàn)gorm框架

2023-05-11 07:25:57

ReduxMiddleware函數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號