React的setState分析

来源:https://www.cnblogs.com/isLiu/archive/2018/01/19/8313063.html
-Advertisement-
Play Games

前端框架層出不窮,不過萬變不離其宗,就是從MVC過渡到MVVM。從數據映射到DOM,angular中用的是watcher對象,vue是觀察者模式,react就是state了。 React通過管理狀態實現對組件的管理,通過this.state()方法更新state。當this.setState()被調 ...


前端框架層出不窮,不過萬變不離其宗,就是從MVC過渡到MVVM。從數據映射到DOM,angular中用的是watcher對象,vue是觀察者模式,react就是state了。

React通過管理狀態實現對組件的管理,通過this.state()方法更新state。當this.setState()被調用的時候,React會重新調用render方法來重新渲染UI。

本文針對React的SetState的源碼來進行解讀,根據陳屹老師的《深入React技術棧》加上自己的理解。

1. setState非同步更新


setState通過一個隊列機制實現state的更新。當執行setState時,會把需要更新的state合併後放入狀態隊列,而不會立刻更新this.state,利用這個隊列機制可以高效的批量的更新state。

// 將新的 state 合併到狀態更新隊列中
var nextState = this._processPendingState(nextProps, nextContext);

// 根據更新隊列和 shouldComponentUpdate 的狀態來判斷是否需要更新組件 var shouldUpdate =
 this._pendingForceUpdate ||
!inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext);

如果不通過setState而直接修改this.state的值,就像這樣:this.state.value = 1,那麼該state將不會被放入狀態隊列中,下次調用this.setState並對狀態隊列進行合併時,將會忽略之前直接別修改的state,因此我們應該用setState更新state的值。

2. setState迴圈調用


當調用setState時,實際上會執行enqueueSetState方法,並對partialState以及_pendingStateQueue更新隊列進行合併,最終通過enqueueUpdate執行state更新。

而performUpdateIfNecessary方法獲取_pendingElement、_pendingStateQueue、_pendingForceUpdate,並調用reciveComponent和updateComponent方法進行組件更新。

如果在shouldComponentUpdate或者componentWillUpdate方法中調用setState,此時this._pendingStateQueue != null,則perfromUpdateIfNecessary方法就會調用updateComponent方法進行組件更新,單updateComponent方法又會調用shouldComponentUpdate和componentWillUpdate方法,造成迴圈調用。

接下里我們看看setState的源碼:

// 更新 state
ReactComponent.prototype.setState = function(partialState, callback) {
    this.updater.enqueueSetState(this, partialState);
    if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
    } 
};

enqueueSetState: function(publicInstance, partialState) { 
    var internalInstance = getInternalInstanceReadyForUpdate(
         publicInstance,
        'setState' 
    );
    
    if (!internalInstance) { 
    return;
    }
    
    // 更新隊列合併操作
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    
    queue.push(partialState);
    enqueueUpdate(internalInstance); 
},

// 如果存在 _pendingElement、_pendingStateQueue和_pendingForceUpdate,則更新組件 
performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
        ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
    }
    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
        this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
    } 
}

3. setState調用棧


對setState很瞭解是吧?來看看這個:

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};

結果是:0、0、2、3,我第一次也不懂,怎麼回事呢?

下麵是一個簡化的setState調用棧。

我們說過setState最終是通過enqueueUpdate執行state更新,enqueueUpdate代碼如下:

function enqueueUpdate(component) {
    ensureInjected();
    
    // 如果不處於批量更新模式
    if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return; 
    }
    
    // 如果處於批量更新模式,則將該組件保存在 dirtyComponents 中
    dirtyComponents.push(component); 
}

如果isBatchingUpdates也就是處於批量更新模式,就把當前調用了this.setState的組件放入dirtyComponents數組中。否則的話就不是批量更新模式,則對隊列中的所有更新執行batchedUpdates方法。例子中的4次console之所以不同,這裡的邏輯判斷起了關鍵作用。

那batchingStrategy呢?其實他就是一個簡單的對象:

var ReactDefaultBatchingStrategy = { 
    isBatchingUpdates: false,
    
    batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        
        if (alreadyBatchingUpdates) { 
            callback(a, b, c, d, e);
        } else {
            transaction.perform(callback, null, a, b, c, d, e);
        } 
    },
}

註意,batchedUpdates中有個transaction.perform調用,transaction,下麵說。

4. transaction


有人看到這裡,transaction,不就是事務嗎?保證數據一致性要用到的,然後說了幾條事務的特性,什麼原子性、穩定性,但是抱歉,這裡的事務和SQL里的事務不一樣,我個人理解這個事務有點類似於中間件,為什麼叫事務,不知道。

可以畫一張圖理解一下:

事務就是將需要執行的方法用wrapper封裝起來,再通過事務提供的perform方法執行。而再perform之前,先執行所wrapeer中的initialize方法,執行完需要執行的方法後,再執行close方法。一組initialize和close方法稱為一個wrapper,事務支持多個wrapper疊加。

React里涉及到很多高階函數,個人理解這個事務也就是一個高階函數嘛,也就是中間件的思想。

5. 解密setState


剛說了事務,那事務是怎麼導致前面所述的setState的各種不同的輸出呢?

很顯然,我們可以將4次setState簡單規成兩類,componentDidMount是一類,setTimeOut中的又是一類,因為這兩次在不同的調用棧中執行。

我們先看看在componentDidMount中setState的調用棧:

再看看在setTimeOut中的調用棧:

顯然,在componentDidMount中調用setState的調用棧更加複雜,那我們重點看看在componentDidMount中的調用棧,發現了batchedUpdates方法,原來在setState調用之前,就已經處於batchedUpdates執行的事務之中了。

那batchedUpdates方法是誰調用的呢?我們再往上追溯一層,原來是ReactMount.js中的_renderNewRootComponent方法。也就是說,整個將React組件渲染到DOM的過程就處於一個大的事務中了。

接下來就可以理解了,因為在componentDidMount中調用setState時,batchingStrategy的isBatchingUpdates已經被設置為true了,所以兩次setState的結果並沒有立即生效,而是被放進了dirtyComponents中。這也解釋了兩次列印this.state.val都是0的原因,因為新的state還沒被應用到組件中。

在看setTimeOut中的兩次setState,因為沒有前置的batchedUpdate調用,所以batchingStrategy的isBatchingUpdates標誌位是false,也就導致了新的state馬上生效,沒有走到dirtyComponents分支。也就是說,setTimeOut中的第一次執行,setState時,this.state.val為1,而setState完成後列印時this.state.val變成了2。第二次的setState同理。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 主鍵: @tableName --表名 @id 表對應的id SELECT SYSCOLUMNS.name FROM SYSCOLUMNS,SYSOBJECTS,SYSINDEXES,SYSINDEXKEYS WHERE SYSCOLUMNS.id = object_id(@tableName) A ...
  • 學習目標 資料庫管理員(DBA)任務和工具 Oracle軟體安裝 Oracle Grid Infrastructure安裝 Oracle DB安裝 Oracle DB管理員的任務 設計、實施和維護Oracle資料庫任務: 1.評估資料庫伺服器硬體 2.制定Oracle軟體安裝計劃 3.制定資料庫和安 ...
  • 一:需求簡介 之前boss提出一個需求,運行在廣告機上的app,需要完成自動升級的功能,廣告機是非觸摸屏的,不能通過手動點擊,所以app必須做到自動下載,自動安裝升級,並且安裝完成後,app還要繼續運行,最好不藉助其它app來實現以上功能。 二:實現思路 實現這個功能第一個想到的方法就是靜默安裝,由 ...
  • 原文鏈接:一句代碼載入網路圖片到ImageView——Android Picasso 在這裡介紹一個Android框架:Picasso。 picasso是Square公司開源的一個Android圖形緩存庫,地址http://square.github.io/picasso/,可以實現圖片下載和緩存功 ...
  • 這是一個點擊之後反應超時的ANR 初步判斷是系統和服務占用資源太多,引起原生設置的ANR在原生設置“語言和輸入法”界面點擊返回鍵是在10:24:52.563,原生設置是在10:24:52.723結束,公司設置是在10:24:57.238才收到onConfigurationChanged回調,快5秒了 ...
  • 。。。求期末不掛。。。 今天完成了第四題,邏輯稍微有點糾纏,但還好問題不是很多 邏輯其實挺複雜的,也可能我基礎太差,還有就是沒來得及寫註釋,很傷。。。 這裡糾結了蠻久的,一直搞不懂這個remove的用法是什麼,好像我裡面沒寫DOM節點的數值也可以刪除。。。 其實想那個刪除對應數值的節點的時候也卡了很 ...
  • 1, 轉義字元 轉義字元:用於表示網頁中的特殊字元 XHTML不直接輸入符號,建議使用轉義字元。 &nbsp 空格; &copy 版權; &reg 註冊商標 如果輸入連續的空格要使用&的轉義字元&(&amp;)nbsp,即&amp;nbsp; 2, 水平線 <Hr> 水平線 <hr width="5 ...
  • xss攻擊(跨站腳本) 是網站應用程式的安全泄露攻擊,是代碼註入的一種。它允許惡意用戶將代碼註入到網頁上,其他用戶在觀看網頁時就會受到影響。 攻擊原理 其特點是不對伺服器端造成任何傷害,而是通過一些正常的站內交互途徑,例如發佈評論,提交含有 JavaScript 的內容文本。這時伺服器端如果沒有過濾 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...