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