redux源碼解讀

背景

因為就得去實習了。所以打算開始補補坑。比如自己閱讀源碼的計劃。所以今天來聊聊redux的源碼。后續會有redux-thunk和react-redux的源碼閱讀。搞定這些的話,就開始閱讀一個node的庫的源碼了,比如eventproxy和anywhere。

開始

  • 總覽, redux的文件結構

文件看起來貌似不少,其實,要理解redux的內部實現,主要就看 createStore.js

,applyMiddleware.js ,combineReducers.js和compose.js。下面從createStore.js開始看。

  • createStore.js

    export default function createStore(reducer, preloadedState, enhancer) {// 如果第二個參數沒有傳入初始的state,而是傳入了enhancer(為applyMiddleware調用的返回值), 那就將第二個參數,即preloadedState賦值給enhancerif (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}// 如果傳入了enhancer,但enhancer不是一個函數,報錯if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}// 反之, 執行。注意此處。此處意味著,如果createStore的時候傳入了enhancer,是會將createStore傳入enhancer中,執行enhancer, 而enhancer的返回值也是一個函數。具體的可以等到下面我們講解applyMiddleware,看完你就知道到底發生了什么。return enhancer(createStore)(reducer, preloadedState)}// 如果沒傳入enhancer,就繼續下面的邏輯// reducer是要求為一個函數的,如果不是一個函數,報錯if (typeof reducer !== 'function') {throw new Error('Expected the reducer to be a function.')}    ..........// 最后createStore就會返回dispatch,subscribe, getState等幾個常用的apireturn {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable};    
    }
    復制代碼

    ?

    上面的代碼給大家展覽了下createStore這個函數大概做了什么,其實就是封裝了一些api,最后暴露給用戶使用。接下來看一下各個api的實現:

    先看一下私有變量的定義

      let currentReducer = reducer // 就是reducer嘛let currentState = preloadedState // 就是傳入的初始state嘛let currentListeners = [] // 當前的監聽器隊列let nextListeners = currentListeners // 未來的監聽器隊列let isDispatching = false //  標志是否正在dispatch
    復制代碼

    getState : 用來獲取store中的state的。因為redux是不允許用戶直接操作state,對于state的獲取,是得通過getState的api來獲取store內部的state。

      function getState() {// 如果正在dispatch的話, 說明新的state正在計算中,現在的state是舊的,為了確保用戶能獲得新的// state,所以要加一個判斷,如果是正在dispatch的話,就報錯,反之,返回現在的stateif (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 :redux提供了用戶一個監聽state變化的api,這個尤為重要,如果沒有這個api的暴露,react-redux應該就比較實現了。

      function subscribe(listener) {// listener是state變化時的回調,必須是個函數if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')}// 如果是正在dispatch中,就報錯。因為要確保state變化時,監聽器的隊列也必須是最新的。所以監聽器的注冊要在計算新的state之前。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 = falseensureCanMutateNextListeners()// 刪除const index = nextListeners.indexOf(listener)nextListeners.splice(index, 1)}}
    復制代碼

    dispatch : 該函數是與getState對應的,getState是讀,那dispatch就是寫。redux的改動state,只能通過發起一個dispatch,傳達一個action給reducer,reducer會根據action和currentState以及自己的內部實現邏輯,來計算出新的state,從而達到寫的目的。

      function dispatch(action) {// action要求是一個簡單對象,而一個簡單對象就是指通過對象字面量和new Object()創建的對象,如果不是就報錯。if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// reducer內部是根據action的type屬性來switch-case,決定用什么邏輯來計算state的,所以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.')}// 這里就是計算新的state,并賦值給currentStatetry {isDispatching = truecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// state更新了后,就如之前我們所說的subscribe,將注冊的回調都觸發一遍。大家要注意這里,是都觸發一遍哦!這個點了解,react-redux的一些原理會比較容易理解。const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action}
    復制代碼

    以上就是createStore的大致實現。這個函數難度不大,更多的是一個了解redux的入口。咱們從這個入口來一點點挖掘別的代碼的實現。下面先從combineReducers開始

  • combineReducers

    • 這個函數是用來整合多個reducers的, 因為createStore只接受一個reducer。
    • 這個函數分為兩部分,第一部分是檢驗用戶傳入的reducers的準確性。第二部分就是計算state的邏輯。第一部分大家看一看也就了解了為什么,咱們主要看看第二部分
    export default function combineReducers(reducers) {................return function combination(state = {}, action) {if (shapeAssertionError) {throw shapeAssertionError}if (process.env.NODE_ENV !== 'production') {const warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action,unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}// hasChanged來標志是否計算出了新的statelet hasChanged = false// 這個就是存儲新的state的const nextState = {}// emmmm, 就是遍歷每一個reducer,把action傳進去,計算statefor (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,就報錯if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}nextState[key] = nextStateForKey// 此處來判斷是否有新的statehasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 根據該標志,決定返回原來的state, 還是新的statereturn hasChanged ? nextState : state}
    }
    復制代碼

    這個整合的過程就是將所有的reducer存在一個對象里。當dispatch一個action的時候,通過遍歷每一個reducer, 來計算出每個reducer的state, 其中用到的優化就是每遍歷一個reducer就會判斷新舊的state是否發生了變化, 最后決定是返回舊state還是新state。最后得到的state的數據結構類似存reducer的數據結構,就是鍵為reducer的名字,值為對應reducer的值。這個部分其實也不難。下面繼續,我們看看applyMiddleware的實現

  • applyMiddleware

    這個部分就是用來擴展redux的功能的。因為redux的最原始的功能就是操作state,管理state。如果我們需要在這個基礎上,根據需求擴展一些功能,就需要通過使用別人編寫好的中間件,或者自己編寫的中間件來達到需求。比如,發起一個dispatch時,我們為了方便調試,不愿意每次自己手動console.log出這個action,這個時候編寫一個logger中間件,就可以自動打印出每次dispatch發起的action,就很容易方便我們測試。又比如,我們要處理異步的action,就可以使用redux-thunk和redux-saga這類中間件。總之,該函數為我們提供了無限的可能。

    我們一點點來看代碼:

    export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)............return {...store,dispatch}}
    復制代碼

    先看個總覽的,注意到applyMiddleware接受不定數量的中間件,然后返回一個接受一個creatStore作為參數,返回一個函數的函數。還記得我們在creatStore的時候么?那里有個場景就是

      if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)}
    復制代碼

    當enhancer不為空且為函數時,就執行該函數,并return回去,作為creatStore的返回值。而這個enhancer是什么呢?翻看redux的文檔:

    const store = createStore(reducers, applyMiddleware(a, b, c));
    復制代碼

    哦!enhancer就是applyMiddleware的結果,就是一個 creatStore => (...args) = {}的函數。那看下enhancer的代碼:

      return createStore => (...args) => {const store = createStore(...args)// 1、也許有的同學一開始看到這個會有點蒙蔽, 我當時看到也是覺得奇怪, 這個dispatch的邏輯不對勁// 而且, 還把這個dispatch作為middleware的參數傳了進去,代表在中間件時使用dispatch的邏輯是這個// 但是看到下面, dispatch = compose(...chain)(store.dispatch)// 還行, 根據作用域鏈, 我們可以知道在中間件中調用dispatch的時候, 其實就是調用了這個dispatch, 而不是一開始聲明的邏輯// 而這個dispatch是已經經過compose的包裝的了.邏輯到這里的時候就很清楚了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)}// 2、compose是如何將中間件串聯在一起的?// 首先一個最簡單的中間件的格式: store => next => action => {}// 這一行代碼就是傳入了store, 獲得了 next => action => {} 的函數const chain = middlewares.map(middleware => middleware(middlewareAPI))// 這一行代碼其實拆分成兩行// const composeRes = compose(...chain);// dispatch = composeRes(store.dispatch);// 第一行是通過compose, 將一個 這樣 next => action => {} 的數組組合成 (...args) => f(g(b(...args))) 這么一個函數// 第二行通過傳入store.dispatch, 這個store.dispatch就是最后一個 next => action => {}的next參數// 傳入后 (...args) => f(g(b(...args)) 就會執行, 執行時, store.dispacth作為b的next傳入, b函數結果action => {}會作為// g的next傳入, 以此類推. 所以最后dispatch作為有中間件的store的dispatch屬性輸出, 當用戶調用dispatch時, 中間件就會一個一個// 執行完邏輯后, 將執行權給下一個, 直到原始的store.dispacth, 最后計算出新的statedispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
    復制代碼

    跟著上面的注釋,大家應該能弄懂enhancer的原理。我這里總結一下,enhancer接收一個creatStore,會在內部創建一個store,然后對該store進行增強,增強的部位在于dispatch。增強的具體方式是通過compose來構造一個dispatch鏈,鏈的具體形式就是**[中間件1,中間件2, ......, 中間件N, store.dispatch]** ,然后將增強的dispatch作為store新的dispatch暴露給用戶。那用戶每次dispatch的時候,就會依次執行每個中間件,執行完當前的,會將執行權交給下一個,直到reducer中,計算出新的state。

結語

網上講解redux的源碼很多,我這篇也是其中一個,主要是我個人學習源碼后的,一種記錄方式,加深自己印象,也為了之后忘了可以快速重溫。redux其實實現上不難,但是思想上真是精髓。程序員的編碼能力是一個剛需,但是設計思想是要借他山之玉,來攻石的。站在巨人的肩膀上看遠方,希望自己多閱讀他人的源碼,在能了解原理更好運用的同時,以后自己也能創造出好用的輪子。謝謝大家花時間觀看。另外,附源碼地址:github.com/Juliiii/sou… ,歡迎大家star和fork ,也歡迎大家和我討論

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/451859.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451859.shtml
英文地址,請注明出處:http://en.pswp.cn/news/451859.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

sql語句update中多個case/when的寫法

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 又如&#xff1a; update xxxx_xxxx set xxx_typeCASE WHEN xxx_type 0 THENYXLX-0WHEN xxx_type 1 THENYXLX-1WHEN xxx_type 2 THE…

Redis-ha(sentinel)搭建

服務器描述&#xff1a;本次搭建是用來測試&#xff0c;所以是在一臺服務器上搭建三個redis服務&#xff08;一主兩從&#xff09; 服務角色 端口 Redis.conf名稱 sentinel配置文件名稱 sentinel端口 redis日志路徑 sentinel路勁 主(master) 6379 redis.conf sentine…

學成在線--26.課程圖片管理(圖片刪除)

文章目錄一. 需求分析二. API三. 服務端開發1. Dao2. Service3. Controller四. 前端開發1. API方法2. 頁面1.before-remove鉤子方法2.handleRemove鉤子方法一. 需求分析 課程圖片上傳成功后&#xff0c;可以重新上傳&#xff0c;方法是先刪除現有圖片再上傳新圖片&#xff1b;…

警惕開源代碼庫中的安全隱患

最近的一項研究發現&#xff0c; 在調查的31個流行庫&#xff08;框架&#xff09;的1261個版本中&#xff0c;超過三分之一存在已知的安全漏洞&#xff0c;大約四分之一的下載文件已經被污染。 該項研究由Aspect Security和Sonatype發起。Aspect Security是一家評估軟件安全漏…

jsp注釋

jsp注釋 <%--注釋內容--%> html注釋 <!--注釋內容-->

線程間的協作(3)——管道輸入/輸出流

2019獨角獸企業重金招聘Python工程師標準>>> 1.管道輸入/輸出流類 分為兩類&#xff0c;字節流管道類&#xff08;PipedInputStream/PipedOutputStream&#xff09;和字符流管道類&#xff08;PipedReader/ PipedWriter&#xff09;。這兩個IO流實現了可以在不同的任…

windows簡易版本 Redis 使用 demo樣例(ssm框架下)

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 在網上下載 windows 版本 的Redis 。下載了直接解壓出來 &#xff1a; 2. 雙擊 redis-server.exe 啟動服務&#xff08;如下圖&#…

Redhat7.3安裝配置Telnet詳細教程

請參考&#xff1a;https://blog.csdn.net/weixin_39934520/article/details/84835949 謝謝樓主分享&#xff01;

程序員的半衰期只有15年?

曾在Google工作負責過技術工作的科技編輯 Matt Heusser總結了他在Google的生活經歷&#xff0c;得出結論&#xff1a; 作為程序員&#xff0c;你只有15年時間。Matt 寫道當我在Google工作時&#xff0c;發現Google大部分人都是20出頭的年輕人&#xff0c;他們經歷的很多事情都是…

EasyNVR、EasyDSS二次開發之:RTMP、HLS流在web頁面進行無插件播放示例Demo代碼

不管是基于EasyNVR還是EasyDSS&#xff0c;都是支持無插件直播&#xff0c;這也是未來視頻直播的一個趨勢。對于傳統的瀏覽器插件播放誰用誰知道&#xff1b; 以上是軟件自帶播放展示 背景需求 對于EasyNVR和EasyDSS的使用方式大概分為兩大類&#xff0c;一類是直接將軟件作為視…

jsp中%@ % 與% % 與%! %

<% %> 有個符號的&#xff0c;叫做指令用來提供整個JSP 網頁相關的信息&#xff0c;并且用來設定JSP網頁的相關屬性&#xff0c; 例如&#xff1a;網頁的編碼方式、語法、信息等。<% %>這個叫做小腳本&#xff0c;是寫java代碼的<%! %>這個是jsp中腳本聲明&a…

Hadoop的學習路線圖

目錄&#xff1a;.1.Hadoop家族產品2.Hadoop家族學習路線圖 Hadoop家族產品截止到2013年&#xff0c;根據cloudera的統計&#xff0c;Hadoop家族產品已經達到20個&#xff01;接下來&#xff0c;我把這20個產品&#xff0c;分成了2類。?第一類&#xff0c;是我已經掌握的?第二…

new TypeToken<List>>(){}.getType() 是什么意思

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 項目中代碼&#xff1a; List<AppVersion> redisList new Gson().fromJson(json, new TypeToken<List<AppVersion>…

11--移除重復節點

編寫代碼&#xff0c;移除未排序鏈表中的重復節點。保留最開始出現的節點。 示例1: 輸入&#xff1a;[1, 2, 3, 3, 2, 1] 輸出&#xff1a;[1, 2, 3] 示例2: 輸入&#xff1a;[1, 1, 1, 1, 2] 輸出&#xff1a;[1, 2]

信息圖:程序員/開發人員實際在用哪些工具

BestVendor.com的工作人員在全球范圍內采訪了500名重要開發人員&#xff0c;在調查詢問他們實際使用的工具后&#xff0c;制作了一張信息圖&#xff0c;如下。 這張信息圖覆蓋10個方面的工具&#xff1a;Bug 追蹤、數據庫、開發框架、集成開發環境&#xff08;IDE&#xff09;、…

class特性

每個HTML元素都可以附帶一個class特性。有時候&#xff0c;你希望有一種方法可以指定多個元素并將這些元素和頁面上的其他元素區分出來&#xff0c;而不是單獨指定文檔中的某個元素。 <!DOCTYPE html> <!-- To change this license header, choose License Headers in…

Xcode代碼提示聯想功能失效,按command鍵點不進去類庫,提示“?”

一大早電腦重啟了下&#xff0c;打開項目之后出現了一堆問號&#xff0c;懷疑是Xcode 抽風了&#xff0c;本著懷疑的態度&#xff0c;新建了項目&#xff0c;一波操作下來是正常的&#xff0c;代碼能聯想&#xff0c;command也好使。于是在網上找答案&#xff0c;終于在這里找到…

12-- 缺失的第一個正數

文章目錄1.問題描述2.解題代碼1.問題描述 給你一個未排序的整數數組&#xff0c;請你找出其中沒有出現的最小的正整數。 示例 1: 輸入: [1,2,0] 輸出: 3 示例 2: 輸入: [3,4,-1,1] 輸出: 2 示例 3: 輸入: [7,8,9,11,12] 輸出: 1 提示&#xff1a; 你的算法的時間復雜度應…

java中的private public protected

1、public&#xff1a;public表明該數據成員、成員函數是對所有用戶開放的&#xff0c;所有用戶都可以直接進行調用 2、private&#xff1a;private表示私有&#xff0c;私有的意思就是除了class自己之外&#xff0c;任何人都不可以直接使用&#xff0c;私有財產神圣不可侵…