setState為什麼不會同步更新組件狀態
我特別喜歡這樣「為什麼」的問題,無論做什麼事,不能只是滿足於只是知道一個知識點,而就該多問「為什麼」,我們做技術的更應該這樣。
那麼,今天就來說一說,為什麼setState不能改成同步更新組件狀態呢?
(本文中所說的setState的使用情景都是React中標準用法,對於「非標準用法」導致this.state同步更新的情況,請看後續文章 setState何時同步 - 知乎專欄)
讓我們反過來想,假如setState改成是同步更新狀態,那麼React會是怎樣一副模樣。
假設,我們現在有機會來對React做一個重大設計調整,把setState的功能設定為同步更改this.state,也就是說,當setState函數返回的時候,this.state已經體現了狀態的改變。
那就有兩個設計的問題就直接擺在我們面前。
- setState更新狀態之後要不要觸發一次更新過程?
- 如何去觸發更新過程?
- setState自動觸發同步的組件更新過程;
- setState自動觸發非同步的組件更新過程;
- 乾脆,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里加上公式=,這時候A2里顯示的就是2,接下來,我們把A1改成2,A2里就變成了4。
沒錯,這就是Reactive Programming,因為我們設定好公式之後,只要改變一個地方,另一個地方就自動發生了變化,而不需要去按個按鈕什麼的去調用那個公式。
還記得關於React的那個公式嗎? ,我們的代碼就是那個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 首頁都使用了哪些技術提高訪問速度?