您當前的位置:首頁 > 動漫

Recoil 用法及原理淺析

作者:由 沙塵塵 發表于 動漫時間:2022-12-11

特性

Hooks 元件的狀態管理。不能在類元件裡面使用。使用 useHooks 讀寫狀態和訂閱元件。

支援 ts。

向後相容 React,支援 React 並行模式。

並行模式實際上不是把頁面渲染和響應使用者互動放在不同的執行緒去並行執行,而是把渲染任務分成多個時間片,在使用者輸入的時候可以暫停渲染,達到近似並行的效果。

Recoil 的狀態和衍生狀態都既可以是同步的也可以是非同步的,可結合

處理非同步狀態,也可以用 recoil 暴露的 loadable。

React

用於程式碼分片和非同步獲取資料

const Clock = React。lazy(() => {

console。log(“start importing Clock”);

return import(“。/Clock”);

});

}>

{ showClock ? : null}

分散(原子化)的狀態管理,打平狀態樹,實現精確的元件更新通知。這樣可以避免一處更新,全量渲染。可以對比 Redux 的單一 store ,在 select state 的時候就需要自頂向下逐層讀取,一個 state 改變,全部訂閱 store 的元件都會收到更新通知。

Recoil state tree 與元件樹正交,正交線就是使用 Recoil state 的元件為點連成的線,減小了 state tree 與元件樹的接觸面,也就說 state 只會影響訂閱它的元件。而且,正交線上的不同的元件屬於不同 state 的子節點,state 之間不會相互影響其他 state 訂閱的元件。

Recoil state tree 與元件樹正交

[1]

Recoil 用法及原理淺析

如何使用

Recoil demo:“shope cart”

實現購物車功能,獲取商品列表、新增商品到購物車、提交訂單。這裡面涉及到在商品列表裡面新增到購物車時,購物車元件和商品列表元件要共享購物車狀態,訂單元件要和購物車元件共享訂單狀態,還有請求商品列表和提交訂單請求涉及到的非同步狀態的處理。下面是這個 demo 的預覽圖:

Recoil 用法及原理淺析

先來實現簡單的獲取商品列表、提交到購物車、新增訂單的功能。提交訂單的功能在後面單獨文字解釋原理。

定義 Recoil state:

Src/Store/atom。ts

import {atom} from ‘recoil’

import { getProductList } from ‘。。/service’

import { ProductItem, CartState, OrderItem, OrderState } from ‘。。/const’

// 商品 state

export const productAtom = atom>({

key: ‘productState’,

default: (async () => {

const res: any = await getProductList()

return res。data。products

})() // 返回 promise

})

// 購物車 state

export const cartAtom = atom({

key: ‘cartState’,

default: []

})

// 訂單 state

export const orderAtom = atom({

key: ‘orderState’,

default: []

})

Src/store。selector。ts

import { orderAtom } from ‘。/atoms’

import { selector} from ‘recoil’

// 計算訂單總價,屬於訂單狀態的衍生狀態

export const myOrderTotalCost = selector({

key: ‘myOrderTotalPrice’,

get: ({ get }) => {

const order = get(orderAtom)

return order。reduce((total, orderItem) => total + orderItem。price * orderItem。quantity, 0)

}

})

定義 usehooks,usehooks 封裝更新購物車和訂單的邏輯,以便跨元件複用

Src/store/hooks。ts

import { useEffect, useState } from ‘react’

import {

useRecoilState,

useSetRecoilState,

useResetRecoilState,

Loadable,

useRecoilValue,

useRecoilStateLoadable,

RecoilState,

SetterOrUpdater

} from ‘recoil’

import { cartAtom, orderAtom, orderWillSubmitAtom, orderIDHadSubmitAtom } from ‘。/atoms’

import {submitOrderRes} from ‘。/selector’

import {produce} from ‘immer’

import { ProductItem, CartItem, CartState, OrderItem, OrderState, LoadableStateHandler } from ‘。。/const’

// 新增商品到 cart

export function useAddProductToCart() {

const [cart, setCart] = useRecoilState(cartAtom)

const addToCart = (item: ProductItem) => {

const idx = cart。findIndex(cartItem => item。id === cartItem。id)

if (idx === -1) {

const newItem = {。。。item, quantity: 1}

setCart([。。。cart, newItem])

} else {

setCart(produce(draftCart => {

const itemInCart = draftCart[idx]

itemInCart。quantity ++

}))

}

}

return [addToCart]

}

// 減少 cart 裡的商品的數量

export function useDecreaseProductIncart() {

const setCart = useSetRecoilState(cartAtom)

const decreaseItemInCart = (item: CartItem) => {

setCart(produce(draftCart => {

const {id} = item

draftCart。forEach((item: CartItem, idx: number, _draftCart: CartState) => {

if(item。id === id) {

if (item。quantity > 1) item。quantity ——

else if (item。quantity === 1) {

draftCart。splice(idx, 1)

}

}

})

}))

}

return [decreaseItemInCart]

}

// 刪除 cart 裡的商品

export function useRemoveProductIncart() {

const setCart = useSetRecoilState(cartAtom)

const rmItemIncart = (item: CartItem|ProductItem) => {

setCart(produce(draftCart => {

draftCart = draftCart。filter((_item: CartItem) => _item。id !== item。id)

return draftCart

}))

}

return [rmItemIncart]

}

// 將 cart 裡的商品加到訂單

export function useAddProductToOrder() {

const setOrder = useSetRecoilState(orderAtom)

const [rmItemIncart] = useRemoveProductIncart()

const addToOrder = (item: CartItem) => {

setOrder(produce(draftOrder => {

draftOrder = [。。。draftOrder, {。。。item, orderID: Math。random()}]

return draftOrder

}))

// 從購物車刪除掉

rmItemIncart(item)

}

return [addToOrder]

}

在商品、購物車、訂單元件裡面引入 Recoil state 和 useHooks,使元件訂閱 Recoil state

這裡只給出商品列表元件,限於篇幅,購物車和訂單元件訂閱 Recoil state 的方法與商品列表元件類似

src/pages/product。tsx

import React from ‘react’

import {useRecoilValueLoadable, useRecoilValue} from ‘recoil’

import {productAtom, cartAtom} from ‘。。/store/atoms’

import {useAddProductToCart, useRemoveProductIncart} from ‘。。/store/hooks’

import { ProductItem } from ‘。。/const’

import ‘。。/style/products。css’

function ProductListLoadable(): JSX。Element {

// productsLoadable 會被快取,即商品請求結果會被快取,

// 重複使用 useRecoilValueLoadable(productAtom) 不會發起重複請求

const productsLoadable = useRecoilValueLoadable(productAtom)

const cart = useRecoilValue(cartAtom)

const [addToCart] = useAddProductToCart()

const [rmItemInCart] = useRemoveProductIncart()

// 可以用 React 的 Suspense 代替下面的 switch 來處理非同步狀態

switch (productsLoadable。state) {

case ‘hasValue’:

const products: Array = productsLoadable。contents

return

{

products

。map((product:ProductItem) =>

Recoil 用法及原理淺析

{product。name}

{product。price} 元

{

cart。findIndex(

itemCart => itemCart。id === product。id

) === -1

addToCart(product)}>

加入購物車

rmItemInCart(product)}>

從購物車刪除

}

}

case ‘hasError’:

return

請求出錯

case ‘loading’:

default:

return

正在載入中……

}

}

export default function ProductList() {

return

商品列表

}

程式碼裡面省略了提交訂單的部分,這裡單獨文字講述利用 Recoil selector 實現這部分功能的原理:因為 selector 是一個純函式,而且 selector 會訂閱其他 atom 或者 selector ,當訂閱的資料發生變化,selector 會自動執行 get,返回結果,所以可以定義一個待提交的訂單的 atom 叫做

orderWillSubmitAtom

,當向這個 atom 新增訂單,那麼依賴這個atom 的 selector 會自動獲取這個訂單並執行訂單提交請求,最後我們在定義一個 useHooks,用於處理這個 selector 的返回值。所以最後提交訂單的這個功能,只需要把提交訂單到

orderWillSubmitAtom

的 api 暴露給 UI 元件即可,達到 UI 與邏輯分離。

基本 API

Recoil 用法及原理淺析

下面來結合原始碼分析核心 api

1.<RecoilRoot/>

用法:

Recoil 狀態的上下文,可以有多個

共存,如果幾個

巢狀,那麼裡面

的 Recoil state 會覆蓋外面的同名的 Recoil state 。Recoil state 的名字由 key 確定,也就是

atom

selector

的 key 值。在一個

中每個

atom

selector

的 key 值應該是唯一的。

使用 React Context 初始化 Recoil state 上下文:

// 中間省略部分程式碼

const AppContext = React。createContext({current: defaultStore});

const useStoreRef = (): StoreRef => useContext(AppContext);

function RecoilRoot({

initializeState_DEPRECATED,

initializeState,

store_INTERNAL: storeProp, // For use with React “context bridging”

children,

}: Props): ReactElement {

// 中間省略部分程式碼

const store: Store = storeProp ?? {

getState: () => storeState。current,

replaceState,

getGraph,

subscribeToTransactions,

addTransactionMetadata,

};

const storeRef = useRef(store);

// 。。。

// Cleanup when the is unmounted

useEffect(

() => () => {

for (const atomKey of storeRef。current。getState()。knownAtoms) {

cleanUpNode(storeRef。current, atomKey);

}

},

[],

);

return (

{children}

);

}

module。exports = {

useStoreRef,

// 。。。

RecoilRoot,

// 。。。

};

包裹的子元件能呼叫

useStoreRef

獲取到放在 context value 裡的 store,原始碼裡有兩層 Context。provider,主要看第一層

第二層

是為了在未來相容 React 併發模式而新近增加的,它在跨 React 根元件共享 Recoil 狀態時,從 Recoil 狀態上下文中分離出可變源以單獨儲存(

Separate context for mutable source #519

),可變源就是指除開 React state、Recoil state 這些不可變狀態之外的資料來源,比如 window。location 。這個和 React 新 API

useMutableSource()

有關,

useMutableSource()

是為了在 React 的併發模式下能夠安全和高效的訂閱可變源而新加的,詳情移步

useMutableSource RFC

裡面 storeRef 就是 Recoil state 上下文,它由

React。useRef()

生成,也就是把 Recoil state 放在

ref。current

,如下:

const store: Store = storeProp ?? {

getState: () => storeState。current,

replaceState,

getGraph,

subscribeToTransactions,

addTransactionMetadata,

};

const storeRef = useRef(store);

解除安裝時清除 Recoil state 避免記憶體洩漏:

// Cleanup when the is unmounted

useEffect(

() => () => {

for (const atomKey of storeRef。current。getState()。knownAtoms) {

cleanUpNode(storeRef。current, atomKey);

}

},

[],

);

還有一個元件

用於在每次更新 Recoil state 的時候通知元件更新,具體的原理下面會講到。

2.Recoil state: atom、selector

用法:

const productAtom = atom({

key: ‘productState’,

default: []

})

const productCount = selector({

key: ‘productCount’,

get: ({get}) => {

const products = get(productAtom)

return products。reduce((count, productItem) => count + productItem。count, 0)

},

set?:({set, reset, get}, newValue) => {

set(productAtom, newValue)

}

})

atom

即 Recoil state。

atom

的初始值在 default 引數設定,值可以是 確定值、Promise、其他 atom、 selector。

selector

是純函式,用於計算 Recoil state 的派生資料並做快取。

selector

是 Recoil derived state (派生狀態),它不僅僅可以從

atom

衍生資料,也可以從其他

selector

衍生資料。

selector

的屬性 get 的回撥函式 get 的引數*(引數為 atom或者selector)*會成為

selector

的依賴 ,當依賴改變的時候,

selector

就會重新計算得到新的衍生資料並更新快取。如果依賴不變就用快取值。selector 的返回值由 get 方法返回,可以返回計算出的確定的值,也可以是 Promise、其他 selector、atom, 如果

atom

selector

的值是 Promise ,那麼表示狀態值是非同步獲取的,Recoil 提供了專門的 api 用於獲取非同步狀態,並支援和 React suspense 結合使用顯示非同步狀態。

atom

selector

都有一個 key 值用於在 Recoil 上下文中唯一標識。key 值在同一個

上下文裡必須是唯一的。

判斷依賴是否改變:由於依賴根本上就是 atom,所以當設定 atom 值的時候就知道依賴改變了,此時就會觸發 selector 重新計算 get。下面的

myGet

就是

selector

的 get:

function myGet(store: Store, state: TreeState): [DependencyMap, Loadable] {

initSelector(store);

return [

new Map(),

detectCircularDependencies(() =>

getSelectorValAndUpdatedDeps(store, state),

),

];

}

getSelectorValAndUpdatedDeps

執行後返回裝載了衍生資料計算結果的 loadable,如果有快取就返回快取,沒有快取就重新計算:

function getSelectorValAndUpdatedDeps(

store: Store,

state: TreeState,

): Loadable {

const cachedVal = getValFromCacheAndUpdatedDownstreamDeps(store, state);

// 如果有快取

if (cachedVal != null) {

setExecutionInfo(cachedVal);

return cachedVal;

}

// 省略了一些程式碼

// 沒有快取

return getValFromRunningNewExecutionAndUpdatedDeps(store, state);

}

進到上面最後一行的函式

getValFromRunningNewExecutionAndUpdatedDeps(store, state)

,它用於計算 selector 的 屬性 get 的回撥函式 get ,計算出新值,快取新值,同時更新依賴:

function getValFromRunningNewExecutionAndUpdatedDeps(

store: Store,

state: TreeState,

): Loadable {

const newExecutionId = getNewExecutionId();

// 計算 selector 的 get ,返回裝載了新的計算結果的 loadable

const [loadable, newDepValues] = evaluateSelectorGetter(

store,

state,

newExecutionId,

);

setExecutionInfo(loadable, newDepValues, newExecutionId, state);

maybeSetCacheWithLoadable(

state,

depValuesToDepRoute(newDepValues),

loadable,

);

notifyStoreWhenAsyncSettles(store, loadable, newExecutionId);

return loadable;

}

3. 設定和獲取 Recoil state 的 hooks API :

暴露出來的用於獲取和設定 Recoil state 的 Hooks:

讀 Recoil state:

useRecoilValue

useRecoilValueLoadable

寫 Recoil state:

useSetRecoilState

useResetRecoilState

讀和寫 Recoil state:

useRecoilState

useRecoilStateLoadable

useRecoilCallback

其中

useRecoilState

=

useRecoilValue

+

useSetRecoilState

,後兩個就是獲取 Recoil state 的只讀狀態值和設定狀態值。

useResetRecoilState

用於重置 Recoil state 為初始狀態,也就是 default 值。

useRecoilStateLoadable

用於獲取非同步狀態和設定狀態,

useRecoilValueLoadable

只讀非同步狀態

useRecoilCallback

:上面的幾個 api 都會讓元件去訂閱 state,當 state 改變元件就會更新,但是使用

useRecoilCallback

可以不用更新元件。

useRecoilCallback

允許元件不訂閱 Recoil state 的情況下獲取和設定 Recoil state,也就說元件可以獲取 state 但是不用更新,反過來元件不必更新自身才能獲取新 state,將讀取 state 延遲到我們需要的時候而不是在元件 monted 的時候。

用法:

const [products, setProducts] = useRecoilState(productAtom)

我們以

useRecoliState

舉例,看一下 Recoil 中設定 Recoil state 值走的流程:

// Recoil_Hooks。ts

function useRecoilState

recoilState: RecoilState

): [T, SetterOrUpdater] {

if (__DEV__) {

// $FlowFixMe[escaped-generic]

validateRecoilValue(recoilState, ‘useRecoilState’);

}

return [useRecoilValue(recoilState), useSetRecoilState(recoilState)];

}

useRecoiState

返回

useRecoilValue

useSetRecoilState

。分別看一下後面兩個。

useSetRecoilState 設定 Recoil state

​ 首先用於設定狀態的

useSetRecoilState

,看它是怎麼把新狀態更新到 store,下面是這個過程的呼叫鏈 :

useSetRecoilState

呼叫

setRecoilValue

,呼叫

useStoreRef()

獲取 Recoil state 上下文,即 store,可以看到實際上

useSetRecoilState

返回的 setter 除了可以傳入新的狀態值,也可以傳入一個回撥函式,用於返回新的狀態值:

function useSetRecoilState(recoilState: RecoilState): SetterOrUpdater {

if (__DEV__) {

// $FlowFixMe[escaped-generic]

validateRecoilValue(recoilState, ‘useSetRecoilState’);

}

const storeRef = useStoreRef();

return useCallback( // 返回 setter

(newValueOrUpdater: (T => T | DefaultValue) | T | DefaultValue) => {

setRecoilValue(storeRef。current, recoilState, newValueOrUpdater);

},

[storeRef, recoilState],

);

}

setRecoilValue

調

queueOrPerformStateUpdate

function setRecoilValue

store: Store,

recoilValue: AbstractRecoilValue

valueOrUpdater: T | DefaultValue | (T => T | DefaultValue),

): void {

queueOrPerformStateUpdate(

store,

{

type: ‘set’,

recoilValue,

valueOrUpdater,

},

recoilValue。key,

‘set Recoil value’,

);

}

queueOrPerformStateUpdate

調

applyActionsToStore

,這裡會把更新排隊或者立即執行更新,排隊是為了批處理多個更新,由於批處理 API 暫時被標記為 unstable,所以這裡不分析,只看立即更新:

function queueOrPerformStateUpdate(

store: Store,

action: Action

key: NodeKey,

message: string,

): void {

if (batchStack。length) {// 排隊批處理

const actionsByStore = batchStack[batchStack。length - 1];

let actions = actionsByStore。get(store);

if (!actions) {

actionsByStore。set(store, (actions = []));

}

actions。push(action);

} else { // 立即更新

Tracing。trace(message, key, () => applyActionsToStore(store, [action]));

// 執行 applyActionsToStore

}

}

applyActionsToStore

調

applyAction

function applyActionsToStore(store, actions) {

store。replaceState(state => {

const newState = copyTreeState(state);

for (const action of actions) {

applyAction(store, newState, action);

}

invalidateDownstreams(store, newState);

return newState;

});

}

// 。。。。。

function copyTreeState(state) {

return {

。。。state,

atomValues: new Map(state。atomValues),

nonvalidatedAtoms: new Map(state。nonvalidatedAtoms),

dirtyAtoms: new Set(state。dirtyAtoms),

};

}

// store。replaceState

const replaceState = replacer => {

const storeState = storeRef。current。getState();

startNextTreeIfNeeded(storeState);//

// Use replacer to get the next state:

const nextTree = nullthrows(storeState。nextTree);

let replaced;

try {

stateReplacerIsBeingExecuted = true;

// replaced 為新的 state tree

replaced = replacer(nextTree);

} finally {

stateReplacerIsBeingExecuted = false;

}

// 如果新、舊 state tree 淺比較相等,就不更新元件

// 實際上,如果不是批處理更新,這裡 replaced 、nextTree 引用不可能相等

if (replaced === nextTree) {

return;

}

if (__DEV__) {

if (typeof window !== ‘undefined’) {

window。$recoilDebugStates。push(replaced); // TODO this shouldn‘t happen here because it’s not batched

}

}

// 如果新、舊 state tree 淺比較不相等,就更新元件,更新會被 React 推入排程

// Save changes to nextTree and schedule a React update:

storeState。nextTree = replaced; // 更新 state tree

nullthrows(notifyBatcherOfChange。current)(); // notifyBatcherOfChange。current() 就是 setState({})

};

applyAction

調

setNodeValue

,後者返回 depMap 和 writes,從下面的分析中知道 depMap 就是一個空的

new Map()

,writes 也是 Map,但是不是空 Map,而是設定了一個元素,元素的 key 是 atom key,value 是 一個 Loadable,Loadable 中裝載了 atom 的狀態值 :

function applyAction(store: Store, state: TreeState, action: Action) {

if (action。type === ‘set’) {

const {recoilValue, valueOrUpdater} = action;

const newValue = valueFromValueOrUpdater(

store,

state,

recoilValue,

valueOrUpdater,

);

const [depMap, writes] = setNodeValue(

store,

state,

recoilValue。key,

newValue,

);

saveDependencyMapToStore(depMap, store, state。version);

// setNodeValue

for (const [key, loadable] of writes。entries()) {

writeLoadableToTreeState(state, key, loadable);

}

}

else if (action。type === ‘setLoadable’){。。。。。}

// 下面省略若干 else if

}

​這裡

writeLoadableToTreeState

把狀態值以

[key, Loadable]

寫入

state。atomValues

function writeLoadableToTreeState(

state: TreeState,

key: NodeKey,

loadable: Loadable

): void {

if (

loadable。state === ‘hasValue’ &&

loadable。contents instanceof DefaultValue

) {

state。atomValues。delete(key);

} else {

state。atomValues。set(key, loadable);

}

state。dirtyAtoms。add(key);

state。nonvalidatedAtoms。delete(key);

}

​後面讀取狀態時就可以像這樣讀:

const loadable = state。atomValues。get(key)

setNodeValue

調

node。set

function setNodeValue

store: Store,

state: TreeState,

key: NodeKey,

newValue: T | DefaultValue,

): [DependencyMap, AtomValues] {

const node = getNode(key);

if (node。set == null) {

throw new ReadOnlyRecoilValueError(

`Attempt to set read-only RecoilValue: ${key}`,

);

}

return node。set(store, state, newValue);

}

node。set

中 node 從 nodes 的元素(

nodes。get(key)

), nodes 是一個 Map 結構

const nodes: Map> = new Map();

const recoilValues: Map> = new Map();

​ nodes 的每個 node 是這樣的物件結構:

{

key: NodeKey,

// Returns the current value without evaluating or modifying state

peek: (Store, TreeState) => ?Loadable

// Returns the discovered deps and the loadable value of the node

get: (Store, TreeState) => [DependencyMap, Loadable],

set: ( // node。set

store: Store,

state: TreeState,

newValue: T | DefaultValue,

) => [DependencyMap, AtomValues],

// Clean up the node when it is removed from a

cleanUp: Store => void,

// Informs the node to invalidate any caches as needed in case either it is

// set or it has an upstream dependency that was set。 (Called at batch end。)

invalidate?: TreeState => void,

shouldRestoreFromSnapshots: boolean,

dangerouslyAllowMutability?: boolean,

persistence_UNSTABLE?: PersistenceInfo,

}

​那麼 node 是什麼時候初始化的呢? 在初始化一個 Recoil state(呼叫 atom 或 selector) 的時候會呼叫一個

registerNode

在 nodes 中註冊一個 node:

function registerNode(node: Node): RecoilValue {

// 。。。

nodes。set(node。key, node);

// 。。。

recoilValues。set(node。key, recoilValue);

// 。。。

}

​以在初始化 atom 為例,初始化一個 atom 時會呼叫

registerNode

註冊一個 node,此時

node。set

是這樣的,它沒有直接把新狀態值 newValue 寫入到 node,而是先用

loadableWithValue

封裝了一層:

function mySet(

store: Store,

state: TreeState,

newValue: T | DefaultValue,

): [DependencyMap, AtomValues] {

initAtom(store, state, ‘set’);

// Bail out if we‘re being set to the existing value, or if we’re being

// reset but have no stored value (validated or unvalidated) to reset from:

if (state。atomValues。has(key)) {

const existing = nullthrows(state。atomValues。get(key));

if (existing。state === ‘hasValue’ && newValue === existing。contents) {

return [new Map(), new Map()];

}

} else if (

!state。nonvalidatedAtoms。has(key) &&

newValue instanceof DefaultValue

) {

return [new Map(), new Map()];

}

if (__DEV__) {

if (options。dangerouslyAllowMutability !== true) {

deepFreezeValue(newValue);

}

}

// can be released now if it was previously in use

cachedAnswerForUnvalidatedValue = undefined;

return [new Map(), new Map()。set(key, loadableWithValue(newValue))];

}

​我們順便看看

node。get

,這個在後面讀取狀態值的是歐惠用到,它會直接返回裝載著狀態值的 Loadable,

state。atomValues。get(key))

讀取到的就是 loadable,因為寫入狀態的時候就是

[key, Loadable]

這樣的形式寫入 node Map 的。

function myGet(store: Store, state: TreeState): [DependencyMap, Loadable] {

initAtom(store, state, ‘get’);

if (state。atomValues。has(key)) {

// Atom value is stored in state:

return [new Map(), nullthrows(state。atomValues。get(key))];

}

// 。。。。

}

我們可以看看

loadableWithValue(newValue)

這個方法,

loadableWithValue(newValue)

返回一個裝載確定的狀態值的 loadable,除了

loadableWithValue(newValue)

,還有

loadableWithError(error)

loadableWithPromise

,這些就是讀取非同步狀態時呼叫的,用來封裝 Loadable :

function loadableWithValue(value: T): Loadable {

// Build objects this way since Flow doesn‘t support disjoint unions for class properties

return Object。freeze({

state: ’hasValue‘,

contents: value,

。。。loadableAccessors,

});

}

function loadableWithError(error: Error): Loadable {

return Object。freeze({

state: ’hasError‘,

contents: error,

。。。loadableAccessors,

});

}

function loadableWithPromise(promise: LoadablePromise): Loadable {

return Object。freeze({

state: ’loading‘,

contents: promise,

。。。loadableAccessors,

});

}

useRecoilValue讀取狀態

useRecoilValue

調

useRecoilValueLoadable

, 後者把狀態封裝到一個 Loadable 並返回,再呼叫

handleLoadable

讀取 Loadable 裝載的 狀態值 :

function useRecoilValue(recoilValue: RecoilValue): T {

if (__DEV__) {

// $FlowFixMe[escaped-generic]

validateRecoilValue(recoilValue, ’useRecoilValue‘);

}

const loadable = useRecoilValueLoadable(recoilValue);

// $FlowFixMe[escaped-generic]

return handleLoadable(loadable, recoilValue, storeRef);

}

2。 `useRecoilValueLoadable` 內部呼叫 `getRecoilValueAsLoadable`:

function useRecoilValueLoadable

recoilValue: RecoilValue

): Loadable {

// 。。。

return getRecoilValueAsLoadable(storeRef。current, recoilValue);

}

getRecoilValueAsLoadable

又呼叫

getNodeLoadable

,後者最終返回 Loadable:

unction getRecoilValueAsLoadable

store: Store,

{key}: AbstractRecoilValue

treeState: TreeState = store。getState()。currentTree,

): Loadable {

// Reading from an older tree can cause bugs because the dependencies that we

// discover during the read are lost。

const storeState = store。getState();

// 。。。

const [dependencyMap, loadable] = getNodeLoadable(store, treeState, key);

// 。。。

return loadable;

}

getNodeLoadable

內部直接用

node。get

返回 loadable, :

function getNodeLoadable

store: Store,

state: TreeState,

key: NodeKey,

): [DependencyMap, Loadable] {

return getNode(key)。get(store, state);

// node。get

}

我們可以返回看看上面

node。get

的定義,內部就是用

state。atomValues。get(key)

返回 loadable。

觸發元件更新(元件如何訂閱狀態)

看完了 Recoil 內部設定和讀取 state 的流程,再來看 Recoil 怎麼在 state 改變時通知元件更新,Recoil 定義了一個

Batcher

元件,只要 Recoil state 改變,就呼叫一次

setState({})

,它沒有往 React 中設定任何狀態,作用只是觸發 React 把此次更新放入到佇列中等待 React commit 階段批次更新。

setState({})

雖然是空的,但是 React 內部會精確的找到呼叫 setState({})的元件,只更新這個元件,而不會影響到其他元件。

function Batcher(props: {setNotifyBatcherOfChange: (() => void) => void}) {

const storeRef = useStoreRef();

const [_, setState] = useState([]);

props。setNotifyBatcherOfChange(() => setState({}));

// 。。。。

return null;

}

Batcher

被放在

下面成為其 children,返回文章最上面

的原始碼可以看到是這樣寫的:

{children}

Batcher

的 props

setNotifyBatcherOfChange

,其傳入的引數

x

() => setState({})

const notifyBatcherOfChange = useRef void)>(null);

function setNotifyBatcherOfChange(x: mixed => void) {

notifyBatcherOfChange。current = x;

}

Recoil state 改變時呼叫

notifyBatcherOfChange。current

() => setState({})

onst replaceState = replacer => {

// 。。。。

nullthrows(notifyBatcherOfChange。current)();

};

這個

replaceState

就是在設定 Recoil state 的流程中被呼叫的,可以返回到上面設定 Recoil state 中呼叫

applyActionsToStore

這一步。所以每次呼叫

useSetRecoilState

設定 Recoil state 都會呼叫 React 的

setState

觸發更新,交給 React 來批次更新。

總結

這裡對 Recoil 的用法和幾個穩定的 API 做了簡單的分析,對於 Recoil 怎麼批次更新和支援 React 並行模式等等沒有涉及。

參考

Recoil 官網:

https://

recoiljs。org/

[1] 圖片來自:

Recoil: A New State Management Library Moving Beyond Redux and the Context API

Recoil 用法及原理淺析

標簽: STATE  const  recoil  Store  Key