React的Suspense功能,簡單說就是讓元件渲染遇到需要非同步操作的時候,可以無縫地“懸停”(suspense)一下,等到這個非同步操作有結果的時候,再無縫地繼續下去。
這裡所說的非同步操作,可以分為兩類:
很好辨認,寫程式嘛,折騰的無外乎就是“程式碼”和“資料”這兩樣東西。
為什麼要非同步載入程式碼呢?
本來,程式碼打包成一個檔案就行,但是當代碼量很龐大,而且也不是所有程式碼都是頁面載入的時候就用得上,把他們拉進唯一的打包檔案,除了打包過程簡單沒有任何好處。所以,為了榨取效能,就要考慮把程式碼打包成若干檔案,這樣每個打包檔案就可以比較小,根據需要來載入。這是一個好主意,不過讓每個開發人員都實現這套機制也是夠扯的,所以,React就用Suspense提供了統一的無縫的程式碼分割(Code Splitting)兼非同步載入方法,在v16.6.0就實現了這樣的Suspense功能。
大家有興趣自己去玩,這種Suspense不是今天要講的重點,今天要講的是“非同步載入資料”的Suspense,也就是利用Suspense來呼叫伺服器API之類的操作。
根據React官方的路線圖,利用Suspense來做資料載入,要等到今年(2019)的中期才釋出,你如果看到這篇文章比較晚,可能已經發布了。
今天要說的,是Suspense做資料載入,和React v16的重頭戲非同步渲染有一點矛盾的地方。
在之前的Live 《深入理解React v16新功能》中我說過,從React v16開始,一個元件的生命週期可以分為兩個階段:render階段+commit階段。在render階段的生命週期函式,因為Fiber的設計特點,可能會被打斷,被打斷之後,會重新被呼叫;而commit階段一旦開始,就絕不會被打斷。render階段和commit階段的分界線是render函式,注意,render函式本身屬於render階段。
舉個例子,一個元件被渲染,執行到函數里面,這時候使用者突然在某個input控制元件裡輸入了什麼,這時候React決定去優先處理input控制元件裡的按鍵事件,就會打斷這個元件的渲染過程,也就是不管返回啥,渲染過程都就此打住,不畫了,專心去處理input控制元件的事情去了。等到那邊的事情處理完,再來渲染這個元件,但是這時候從原來位置重新開始,那肯定是不靠譜的,因為剛才的按鍵事件處理可能改變了一些狀態,為了保證絕對靠譜,React決定……還是從頭走一遍吧, 於是,重新去調、然後呼叫。
看到沒喲,render之前的生命週期函式都會被呼叫,而且,因為這種“打斷”是完全是不可預期的,所以,現在就要求在render階段的所有生命週期函式不要做有副作用的操作。
什麼叫副作用?就是純函式不該做的操作。
什麼叫純函式?就是除了根據輸入引數返回結果之外,不做任何多與事情的操作。
如果一個函式修改全域性變數,那就不是一個純函式;如果一個函式修改類例項狀態,那就不是一個純函式;如果一個函式丟擲異常,那就不是一個純函式;如果一個函式透過AJAX訪問伺服器API,那就不是一個純函式。
就拿訪問伺服器API為例,假如render階段的生命週期函式做了訪問伺服器API的AJAX操作,那麼,很有可能產生連續對伺服器的訪問,因為非同步渲染下render階段會被打斷而重複執行啊。
再說一遍,在render階段的所有生命週期函式不要做有副作用的操作,這些函式必須是純函式。
那麼,現在問題來了,使用Suspense來獲取資料,會不會違反者這個規定呢?
雖然Suspense這方面的API還沒有確定,但是程式碼形式還是明確的,利用試玩版的react-cache展示一下。
這裡就有意思了,使用Suspense來獲取資料,既然資料是在render函式(或者像上面例子一樣在函式型別元件中)使用,那麼獲取資料的過程肯定是在render階段,但是,獲取資料的過程是要呼叫AJAX的啊,AJAX是副作用操作,這不就和“render階段不能做有副作用操作“的規定矛盾了嗎?
的確有點矛盾。
Reac開發人員對這個的解釋是這樣:
簡單說來,就是降低了要求,只需要render階段的操作是”冪等“(indempotent)就可以了。
所謂冪等,就是一次呼叫和N次呼叫產生一樣的結果。
還是舉例來說吧。
上面的foo3這個函式,的確有副作用,但是,利用程式碼巧妙地防一手,只讓第一次呼叫發出AJAX,之後的呼叫就不發AJAX了,這樣,呼叫多少次,產生的效果都一樣,這就是”冪等“。
冪等雖然沒有純函式那麼純,但是也足夠好了,至少對於React這樣無法做到”純“的框架,這也是最好的結果。
試玩版react-cache的函式,接受一個返回Promise的函式為引數,獲取的Promise是會被cache住的,所以,雖然會被打斷重複多次,如果有返回結果,那麼返回的結果都是一樣 ,也就達到了”冪等“的效果。
這麼一看,矛盾也就解決了。
總結一下,實際上我們要把”在render階段的所有生命週期函式不要做有副作用的操作,這些函式必須是純函式“這個要求改一下,改成”在render階段的所有生命週期函式都應該冪等“。
雖然一說React往往都說要說“函數語言程式設計”,但是React真的先天和崇尚純函式的“函數語言程式設計”有很大距離,包括Hooks,表面上看推崇函式式元件,似乎是向函數語言程式設計邁進了一步,但是,所有的Hooks函式都是有狀態的,怎麼能算純函式呢。
當然,有潔癖一樣追求純函式也沒有必要,既然“冪等”能夠解決問題,我們也樂見其成。
印證了那句老話:只要你降低標準,會發現世界豁然開朗:)
P.S. 這篇文章裡的背景知識如果不清楚,就去看我之前的Live吧,該說的知識點我都說過了。
《快速瞭解React的新功能Suspense和Hooks》
《深入理解React v16新功能》
《幫助你深入理解 React》
React的Suspense功能,簡單說就是讓元件渲染遇到需要非同步操作的時候,可以無縫地“懸停”(suspense)一下,等到這個非同步操作有結果的時候,再無縫地繼續下去。
這裡所說的非同步操作,可以分為兩類:
非同步載入程式碼非同步載入資料很好辨認,寫程式嘛,折騰的無外乎就是“程式碼”和“資料”這兩樣東西。
為什麼要非同步載入程式碼呢?
本來,程式碼打包成一個檔案就行,但是當代碼量很龐大,而且也不是所有程式碼都是頁面載入的時候就用得上,把他們拉進唯一的打包檔案,除了打包過程簡單沒有任何好處。所以,為了榨取效能,就要考慮把程式碼打包成若干檔案,這樣每個打包檔案就可以比較小,根據需要來載入。這是一個好主意,不過讓每個開發人員都實現這套機制也是夠扯的,所以,React就用Suspense提供了統一的無縫的程式碼分割(Code Splitting)兼非同步載入方法,在v16.6.0就實現了這樣的Suspense功能。
大家有興趣自己去玩,這種Suspense不是今天要講的重點,今天要講的是“非同步載入資料”的Suspense,也就是利用Suspense來呼叫伺服器API之類的操作。
根據React官方的路線圖,利用Suspense來做資料載入,要等到今年(2019)的中期才釋出,你如果看到這篇文章比較晚,可能已經發布了。
今天要說的,是Suspense做資料載入,和React v16的重頭戲非同步渲染有一點矛盾的地方。
在之前的Live 《深入理解React v16新功能》中我說過,從React v16開始,一個元件的生命週期可以分為兩個階段:render階段+commit階段。在render階段的生命週期函式,因為Fiber的設計特點,可能會被打斷,被打斷之後,會重新被呼叫;而commit階段一旦開始,就絕不會被打斷。render階段和commit階段的分界線是render函式,注意,render函式本身屬於render階段。
舉個例子,一個元件被渲染,執行到函數里面,這時候使用者突然在某個input控制元件裡輸入了什麼,這時候React決定去優先處理input控制元件裡的按鍵事件,就會打斷這個元件的渲染過程,也就是不管返回啥,渲染過程都就此打住,不畫了,專心去處理input控制元件的事情去了。等到那邊的事情處理完,再來渲染這個元件,但是這時候從原來位置重新開始,那肯定是不靠譜的,因為剛才的按鍵事件處理可能改變了一些狀態,為了保證絕對靠譜,React決定……還是從頭走一遍吧, 於是,重新去調、然後呼叫。
看到沒喲,render之前的生命週期函式都會被呼叫,而且,因為這種“打斷”是完全是不可預期的,所以,現在就要求在render階段的所有生命週期函式不要做有副作用的操作。
什麼叫副作用?就是純函式不該做的操作。
什麼叫純函式?就是除了根據輸入引數返回結果之外,不做任何多與事情的操作。
如果一個函式修改全域性變數,那就不是一個純函式;如果一個函式修改類例項狀態,那就不是一個純函式;如果一個函式丟擲異常,那就不是一個純函式;如果一個函式透過AJAX訪問伺服器API,那就不是一個純函式。
就拿訪問伺服器API為例,假如render階段的生命週期函式做了訪問伺服器API的AJAX操作,那麼,很有可能產生連續對伺服器的訪問,因為非同步渲染下render階段會被打斷而重複執行啊。
再說一遍,在render階段的所有生命週期函式不要做有副作用的操作,這些函式必須是純函式。
那麼,現在問題來了,使用Suspense來獲取資料,會不會違反者這個規定呢?
雖然Suspense這方面的API還沒有確定,但是程式碼形式還是明確的,利用試玩版的react-cache展示一下。
這裡就有意思了,使用Suspense來獲取資料,既然資料是在render函式(或者像上面例子一樣在函式型別元件中)使用,那麼獲取資料的過程肯定是在render階段,但是,獲取資料的過程是要呼叫AJAX的啊,AJAX是副作用操作,這不就和“render階段不能做有副作用操作“的規定矛盾了嗎?
的確有點矛盾。
Reac開發人員對這個的解釋是這樣:
簡單說來,就是降低了要求,只需要render階段的操作是”冪等“(indempotent)就可以了。
所謂冪等,就是一次呼叫和N次呼叫產生一樣的結果。
還是舉例來說吧。
上面的foo3這個函式,的確有副作用,但是,利用程式碼巧妙地防一手,只讓第一次呼叫發出AJAX,之後的呼叫就不發AJAX了,這樣,呼叫多少次,產生的效果都一樣,這就是”冪等“。
冪等雖然沒有純函式那麼純,但是也足夠好了,至少對於React這樣無法做到”純“的框架,這也是最好的結果。
試玩版react-cache的函式,接受一個返回Promise的函式為引數,獲取的Promise是會被cache住的,所以,雖然會被打斷重複多次,如果有返回結果,那麼返回的結果都是一樣 ,也就達到了”冪等“的效果。
這麼一看,矛盾也就解決了。
總結一下,實際上我們要把”在render階段的所有生命週期函式不要做有副作用的操作,這些函式必須是純函式“這個要求改一下,改成”在render階段的所有生命週期函式都應該冪等“。
雖然一說React往往都說要說“函數語言程式設計”,但是React真的先天和崇尚純函式的“函數語言程式設計”有很大距離,包括Hooks,表面上看推崇函式式元件,似乎是向函數語言程式設計邁進了一步,但是,所有的Hooks函式都是有狀態的,怎麼能算純函式呢。
當然,有潔癖一樣追求純函式也沒有必要,既然“冪等”能夠解決問題,我們也樂見其成。
印證了那句老話:只要你降低標準,會發現世界豁然開朗:)
P.S. 這篇文章裡的背景知識如果不清楚,就去看我之前的Live吧,該說的知識點我都說過了。
《快速瞭解React的新功能Suspense和Hooks》
《深入理解React v16新功能》
《幫助你深入理解 React》