setState為什麼不會同步更新組件狀態

上次寫了一篇《setState:這個API設計到底怎麼樣》,其中陳述了setState不會同步更新組件狀態的事實,有朋友問:為什麼說「setState肯定還是不能立刻更新this.state,不然React整個概念就被推翻了」?

我特別喜歡這樣「為什麼」的問題,無論做什麼事,不能只是滿足於只是知道一個知識點,而就該多問「為什麼」,我們做技術的更應該這樣。

那麼,今天就來說一說,為什麼setState不能改成同步更新組件狀態呢?

(本文中所說的setState的使用情景都是React中標準用法,對於「非標準用法」導致this.state同步更新的情況,請看後續文章 setState何時同步 - 知乎專欄)

讓我們反過來想,假如setState改成是同步更新狀態,那麼React會是怎樣一副模樣。

假設,我們現在有機會來對React做一個重大設計調整,把setState的功能設定為同步更改this.state,也就是說,當setState函數返回的時候,this.state已經體現了狀態的改變。

那就有兩個設計的問題就直接擺在我們面前。

  1. setState更新狀態之後要不要觸發一次更新過程?

  2. 如何去觸發更新過程?

對這兩個問題,我們有三個選擇答案。

  1. setState自動觸發同步的組件更新過程;
  2. setState自動觸發非同步的組件更新過程;
  3. 乾脆,setState根本不觸發組件更新過程,讓開發者顯示驅動更新過程。

我們逐個看看各種選擇,看他們這些選擇是不是行得通,有什麼優劣。

第一個選擇:setState自動觸發同步的組件更新過程

如果這樣,也就是setState調用返回時,一個完整更新過程已經走完了,這樣的設計,應該是不行的,因為每一次setState都會引發一次組件更新太浪費了啊!

setState引發的組件更新過程,包含生命周期函數有四個。

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

每一次setState調用都走一圈生命周期,光是想一想也會覺得會帶來性能的問題,其實這四個函數都是純函數,性能應該還好,但是render函數返回的結果會拿去做Virtual DOM比較和更新DOM樹,這個就比較費時間。

目前React會將setState的效果放在隊列中,積攢著一次引發更新過程,為的就是把Virtual DOM和DOM樹操作降到最小,用於提高性能。

好吧,我們退一步,就當我們對React和當今CPU和瀏覽器的性能充滿信心,不在乎這點性能損耗,但是,這樣的設計還是問題。

一個shouldComponentUpdate函數的參數是這樣。

shouldComponentUpdate(nextProps, nextState) {n //在這個函數被調用時,this.state還沒有被改變n}n

目前的React設計,shouldComponentUpdate被調用時,this.state並沒有被改變,setState產生的狀態改變是通過參數nextState來體現的,componentWillUpdate也是一樣。

所以,為了讓基於現有React體系的很多代碼實現不要完蛋,我們肯定不能在shouldComponentUpdate之前修改this.state,修改this.state的時機肯定只能是在調用componentWillUpdate和render函數之間。

也就是說,即使setState號稱「同步更新」this.state,實際上還是不能立即更新,因為setState引發的生命周期函數shouldComponentUpdate和componentWillUpdate里,this.state還沒有改變。

聽起來,解決得並不是很徹底,似乎把事情搞得更複雜了。

第二個選擇:setState自動觸發非同步的組件更新過程

這種選擇下,setState返回時,this.state已經被改變了,但是並沒有立即引發更新過程,React依然將setState產生的結果放在隊列里,等到時機合適時走更新過程。

這樣肯定不行啊,如果setState把this.state改了,那shouldComponentUpdate和componentWillUpdate咋辦?這兩個函數一直就假設執行時this.state並沒有被改變啊。

這種選擇無疑是行不通的。

第三個選擇:setState根本不觸發組件更新過程

前兩個選擇都不怎樣,那就看這第三個選擇,setState只修改this.state,並不出發組件更新過程,那我們就需要另外一個函數用來主動觸發更新狀態,可是……如果真的這樣的話,還需要setState幹嗎?

你看,setState既然和組件的更新過程沒有關係,那我們直接操作this.state好了,對不對?

如果你真的喜歡這種方式,其實都不用重新設計React,現在的React就可以這麼玩:直接操作this.state來同步修改組件狀態,讓後調用this.setState,不用任何參數,相當於空放一槍,唯一的目的就是主動觸發一次更新狀態。

這樣,《setState:這個API設計到底怎麼樣》中的incrementMultiple函數就可以這麼寫。

incrementMultiple() {n this.state.count = this.state.count + 1;n this.state.count = this.state.count + 1;n this.state.count = this.state.count + 1;nn this.setState();n }n

三次都是直接讀取this.state.count,三次都是直接修改this.state.count,結果正確,每次調用incrementMultiple真的能讓this.state上的count值增加3,而且組件重行繪製。

覺得怎麼樣?覺得這是一個高招,還是一個陰招?

反正我看到自己寫出來的這招,反應也是:呵呵。

如果用這種方法來寫code,那麼React也就不夠React了,算不上Reactive

React何以稱為React

React雖然並不像Rx.js那樣高舉Reactive Programming(響應式編程)的大旗,但是依然體現了Reactive Programming的思想。

Reactive Programming通俗說就是這樣的編程風格:改變一個東西,另一個東西會做出響應發生改變,而不用我們的Code去主動讓另一個東西做出改變。

大家都用過Excel,Excel就代表了Reactive Programming。

請把想像有這麼一個Excel表格,在一個格子A1里填上1,在另一個格子A2里加上公式=A1times 2,這時候A2里顯示的就是2,接下來,我們把A1改成2,A2里就變成了4。

沒錯,這就是Reactive Programming,因為我們設定好公式之後,只要改變一個地方,另一個地方就自動發生了變化,而不需要去按個按鈕什麼的去調用那個公式。

還記得關於React的那個公式嗎?UI = f(state) ,我們的代碼就是那個f,和Excel表格中的公式一個性質。在React中,當我們通過setState改變了組件狀態,那組件的UI就會自動發生變化,這就是Reactive Programming的體現。

如果我們通過直接修改this.state,然後調用一次setState,就像是改變了Excel表格里A1的值,然後還要再按一個按鈕去改變A2的值……看起來怎麼樣?很不Reactive。

所以,要我說怎麼看上面直接修改this.state.count的方法,就是:咱們都玩上React了,就不要再回到解放前了。

總結一下,看了上面三個「重新設計React」選擇,似乎讓setState同步更新組件狀態不是個好的選擇。

個人建議,大家別直接去操作this.state,一定要抵擋住這個誘惑,不然我會後悔把這個陰招透露出來。

實際上,我很好奇,什麼樣的實際需求希望setState能夠同步修改this.state呢?我並沒有遇到過這種需求,也許是我處理的情況還不夠複雜,但是,我相信肯定有更好的對策。

如果你真的遇到具體場景想要setState同步更改狀態,可以在評論中留言,我會幫大家想一想怎麼樣處理。

【更新】後續文章 setState何時同步 - 知乎專欄


推薦閱讀:

阿里雲前端周刊 - 第 19 期
新人一枚,想進階前端,現在已經掌握了html與css和了解js與jQuery怎麼做。?
互聯網架構設計:高性能的前端
Chrome DevTools之Timeline Tool簡介
Facebook 首頁都使用了哪些技術提高訪問速度?

TAG:React | 前端开发 | API |