第一個可以在條件語句中使用的原生Hook誕生了
大家好,我卡頌。
在10月13日的first-class-support-for-promises RFC[1]中,介紹了一種新的hook? —— use。
use?什么?就是use?,這個hook?就叫use。這也是第一個:
- 可以在條件語句中書寫的hook
- 可以在其他hook回調中書寫的hook
本文來聊聊這個特殊的hook。
use是什么
我們知道,async函數會配合await關鍵詞使用,比如:
類似的,在React組件中,可以配合use起到類似的效果,比如:
可以認為,use的作用類似于:
- async await?中的await。
- generator?中的yield。
use?作為「讀取異步數據的原語」,可以配合Suspense實現「數據請求、加載、返回」的邏輯。
舉個例子,下述例子中,當fetchNote?執行異步請求時,會由包裹Note?的Suspense組件渲染「加載中狀態」。
當請求成功時,會重新渲染,此時note數據會正常返回。
當請求失敗時,會由包裹Note的ErrorBoundary組件處理失敗邏輯。
其背后的實現原理并不復雜:
- 當Note組件首次render,fetchNote發起請求,會throw promise,打斷render流程。
- 以Suspense fallback作為渲染結果。
- 當promise狀態變化后重新觸發渲染。
- 根據note的返回值渲染。
實際上這套「基于promise的打斷、重新渲染流程」當前已經存在了。use的存在就是為了替換上述流程。
與當前React?中已經存在的上述「promise流程」不同,use?僅僅是個「原語」(primitives),并不是完整的處理流程。
比如,use并沒有「緩存promise」的能力。
舉個例子,在下面代碼中fetchTodo?執行后會返回一個promise,use?會消費這個promise。
當Todo組件的id prop變化后,觸發fetchTodo重新請求是符合邏輯的。
但是當isSelected prop變化后,Todo組件也會重新render,fetchTodo執行后會返回一個新的promise。
返回新的promise不一定產生新的請求(取決于fetchTodo的實現),但一定會影響React接下來的運行流程(比如不能命中性能優化)。
這時候,需要配合React提供的cache API(同樣處于RFC)。
下述代碼中,如果id prop不變,fetchTodo始終返回同一個promise:
use的潛在作用
當前,use的應用場景局限在「包裹promise」。
但是未來,use會作為客戶端中處理異步數據的主要手段,比如:
- 處理context
use(Context)?能達到與useContext(Context)?一樣的效果,區別在于前者可以在條件語句,以及其他hook回調內執行。
- 處理state
可以利用use實現新的原生狀態管理方案:
為什么不使用async await
本文開篇提到,use原語類似async await中的await,那為什么不直接使用async await呢?類似下面這樣:
有兩方面原因。
一方面,async await的工作方式與React客戶端處理異步時的邏輯不太一樣。
當await的請求resolve后,調用棧是從await語句繼續執行的(generator中yield也是這樣)。
而在React中,更新流程是從根組件開始的,所以當數據返回后,更新流程是從根組件從頭開始的。
改用async await的方式勢必對當前React底層架構帶來挑戰。最起碼,會對性能優化產生不小的影響。
另一方面,async await這種方式接下來會在Server Component中實現,也就是異步的服務端組件。
服務端組件與客戶端組件都是React組件,但前者在服務端渲染(SSR),后者在客戶端渲染(CSR),如果都用async await,不太容易從代碼層面區分兩者。
總結
use?是一個「讀取異步數據的原語」,他的出現是為了規范React在客戶端處理異步數據的方式。
既然是原語,那么他的功能就很底層,比如不包括請求的緩存功能(由cache處理)。
之所以這么設計,是因為React?團隊并不希望開發者直接使用他們。這些原語的受眾是React生態中的其他庫。
比如,類似SWR?、React-Query?這樣的請求庫,就可以結合use?,再結合自己實現的請求緩存策略(而不是使用React?提供的cache方法)
各種狀態管理庫,也可以將use作為其底層狀態單元的容器。
值得吐槽的是,Hooks?文檔中hook的限制那一節恐怕得重寫了。
參考資料
[1]first-class-support-for-promises RFC:https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md。