Recoil 源碼探索

Hello,豆皮範兒同窗們,字節跳動春招開始了,你們ready了嗎?歡迎你們找我內推,或者進入團隊號投遞簡歷。node

做者:伍然緩存

接下來咱們請來了字節跳動數據平臺的xx帶來的《Recoil 源碼探索》,讓咱們一塊兒學起來,造起來,也是一種不錯的狀態解決方案。markdown

1. 簡介

Recoil 是 facebook 推出的一個狀態解決方案,主要的特色有:1. 高性能的渲染,2. 經過狀態生成派生值。app

2. 特色

高性能渲染

在 List 和 Canvas 中的第二個子組件中,若是有狀態須要進行共享,常見的方法是,將狀態提高到最近的公共祖先節點上,可是這樣帶來的問題是,若是不進行額外的處理,會形成全局的從新渲染。固然,可使用 useMemo + immutable 進行優化。另一種解決方法是使用 Context,使用 Context 若是想解決全局渲染的問題,須要將須要共享的值放在一個獨立的 Context 中,這樣帶來的問題是,咱們的組件上,若是有更多的狀態須要共享,就須要包裹不少的 Provider,維護起來也比較困難。異步

Recoil 帶來了一個全新的概念 Atom,將狀態原子化並分散管理,Atom 是能夠進行更新而且被組件所訂閱的,只要 Atom 更新,能夠精確地對訂閱這個 Atom 的組件進行從新渲染。ide

派生值

能夠根據 Atom,生成新的可被組件訂閱的數據,相似 Vue 的計算屬性。性能

在這裏,更增強大的是,這裏的 get 方法裏還能夠處理異步請求。優化

3. 核心過程源碼

初始化

與 Redux 相似,在使用全局數據時,須要在根組件上包一個組件,在 Recoil 中,須要包裹上組件 RecoilRoot。spa

對於 RecoilRoot,實際上是在咱們的業務代碼上,包了一層 Context,其中提供了獲取 store 中的數據,更新 store 中的數據等方法。3d

原子狀態定義

Recoil中,將每個最小的狀態單位(即沒法從其餘狀態計算得出)定義爲一個 Atom。對於每個 Atom ,其實返回的是一個對象,包括了 key(這個 key 是從傳入的 option 中解構出來的,而且須要全局惟一), 以及提供了 get 和 set 等方法。

對於 Atom 而言,必須有一個 set 方法,由於咱們定義一個原子數據,確定須要對其進行修改,不然是沒有意義的。而對於 Atom 而言,其 get 方法相對簡單,在 key 存在的狀況下,直接從一個狀態的 Map 中獲取便可。

派生值定義

對於 selector,也與 Atom 同樣,也提供了 get 及其餘方法,然而,對於 selector 而言,是能夠經過 Atom 獲得的,因此 set 方法則不是必須的。

對於 get 方法,也與 Atom 有所不一樣,selector 的 get 多了一個緩存的處理,在沒有緩存的狀況下,會根據依賴的 Atom,計算獲得對應的值,並將這個值進行緩存,在下次獲取的時候,若是依賴的值沒有變化,則能夠從緩存中取值,儘量地提升性能。

在組件中 訂閱/更新 共享的值

在 Recoil 中,常見 訂閱/更新 的 hooks 有 useRecoilState, useRecoilValue, useSetRecoilState。其中 useRecoilState 包含了其他兩個。

訂閱值

useRecoilValue 中主要的調用鏈以下:

useRecoilValue -> useRecoilValueLoadable -> useRecoilValueLoadable_LEGACY -> subscribeToRecoilValue
複製代碼

在這裏,組件中經過 useRecoilValue(useRecoilState) 使用 Atom 或是 selector,會在組件上增長一個訂閱,這個訂閱經過 useState 來實現,當對應的 RecoilValue 更新時,對組件進行從新渲染,從而作到最小粒度更新組件。這裏十分重要的是,storeState.nodeToComponentSubscriptions.set 的方法,這個方法在全局的 store 上更新了全局惟一的 key 所維護的 subscription map,經過這個 map ,在進行更新值的時候,能夠準確找到對應關係,從而觸發 forceUpdate 對組件進行從新渲染。

更新值

useSetRecoilState 中主要的調用鏈以下:

useSetRecoilState -> setRecoilValue -> queueOrPerformStateUpdate -> applyActionsToStore -> store.replaceState -> Batcher Effect -> sendEndOfBatchNotifications
複製代碼

這個 store.replaceState 則是在 RecoilRoot 中對 store 進行初始化時設置的方法。

其中的 notifyBatcherOfChange.current 則是在 <Batcher> 組件中經過 useState 的形式進行 state 的更新,從而觸發 useEffect,執行 sendEndOfBatchNotifications

而最終執行的這個 callback,就是上面訂閱值的時候,所註冊的 subscription,從而達到精確更新對應的組件。

這裏須要注意的是,若是組件只須要對共享的狀態進行更新,不能使用 useRecoilState,由於使用 useRecoilState 的同時會進行共享狀態的訂閱,形成沒必要要的渲染。

4. 總結

從 Recoil 的源碼中,能夠了解到其經過訂閱和更新的分離,確保最小粒度的渲染,以及最小化了數據的單位,利用 selector 產生的派生值進行緩存,提升了計算的效率。

相關文章
相關標籤/搜索