selector(選項)
selector 在 Recoil 中表示函式,或「衍生狀態」。可以將它們想像成不帶副作用的「冪等」或「純函式」,且總會為一組指定的依賴項傳回相同的值。如果只提供 get
函式,selector 就是唯讀的,且會傳回 RecoilValueReadOnly
物件。如果也提供了 set
,則會傳回可寫入的 RecoilState
物件。
Recoil 會管理原子和 selector 狀態變更,以了解何時要通知訂閱該 selector 的元件重新渲染。如果 selector 的物件值的變異是直接進行的,可能就會略過這個機制,且不會適當地通知訂閱元件。為了協助偵測錯誤,Recoil 會在開發模式中凍結 selector 值物件。
function selector<T>({
key: string,
get: ({
get: GetRecoilValue,
getCallback: GetCallback,
}) => T | Promise<T> | Loadable<T> | WrappedValue<T> | RecoilValue<T>,
set?: (
{
get: GetRecoilValue,
set: SetRecoilState,
reset: ResetRecoilState,
},
newValue: T | DefaultValue,
) => void,
dangerouslyAllowMutability?: boolean,
cachePolicy_UNSTABLE?: CachePolicy,
})
type ValueOrUpdater<T> = T | DefaultValue | ((prevValue: T) => T | DefaultValue);
type GetCallback =
<Args, Return>(
callback: CallbackInterface => (...Args) => Return,
) => (...Args) => Return;
type GetRecoilValue = <T>(RecoilValue<T>) => T;
type SetRecoilState = <T>(RecoilState<T>, ValueOrUpdater<T>) => void;
type ResetRecoilState = <T>(RecoilState<T>) => void;
type CachePolicy =
| {eviction: 'lru', maxSize: number}
| {eviction: 'keep-all'}
| {eviction: 'most-recent'};
key
- 在內部用來辨識 selector 的唯一字串。這個字串對於整個應用程式的其他原子和 selector 而言應為唯一的。如果用於永續性,則執行期間需要保持穩定。獲得
- 一種評估派生狀態值的函數。它可以直接傳回一個值,異步Promise
、可載入
或表示相同型態的其他 atom 或 selector。若要將 selector 值直接設定為類似Promise
或可載入
的設定項,可以透過selector.value(...)
函數將其封裝。這個回呼會接收到一個物件作為第一個參數,包含下列屬性設定?
- 如果設定這個屬性,selector 將會傳回可寫入的狀態。一個函數,它會傳遞一個物件作為第一個參數,這個物件包含回呼,以及新的輸入值。輸入值可能是T
型別的值,或者可能是DefaultValue
型別的物件,表示使用者重設了 selector。回呼包括get()
- 用於從其他 atom/selector 取回值的函數。這個函數不會讓 selector 訂閱給定的 atom/selector。設定()
- 用於設定上游 Recoil 狀態值的函數。第一個參數是 Recoil 狀態,第二個參數是新的值。新值可能是更新函數或DefaultValue
物件,以傳播重設動作。重設()
- 用於重設上游 Recoil 狀態的預設值的函數。唯一的參數是 Recoil 狀態。
dangerouslyAllowMutability
- 在某些情況下,允許變更儲存在 selector 中且不表示狀態變更的物件可能是值得的。請使用這個選項來覆寫開發模式中的凍結物件。快取政策_UNSTABLE
- 定義內部 selector 快取的行為。有助於控制在 selector 中擁有許多變更相依項的應用程式的記憶體使用量。驅逐
- 可以設定為lru
(需要設定maxSize
)、keep-all
(預設值)或most-recent
。當快取大小超過maxSize
時,lru
快取會從 selector 快取中驅逐最近最少使用的值。keep-all
政策表示 selector 中的所有依賴項及其值都將無限期地儲存在 selector 快取中。most-recent
政策會使用大小為 1 的快取,而且只保留最近儲存的依賴項及其值。- 請注意,快取儲存選擇器的值,基礎是包含所有相依性和其值的密鑰。這表示,內部選擇器快取的大小取決於選擇器值的長度,以及所有相依性的獨特長度。
- 請注意,未來可能會變更預設驅逐政策(目前為
keep-all
)。
具有簡易靜態相依性的選擇器
const mySelector = selector({
key: 'MySelector',
get: ({get}) => get(myAtom) * 100,
});
動態相依性
唯讀選擇器具有 get
方法,可根據相依性評估選擇器的值。如果其中任何一個相依性更新,則選擇器將重新評估。根據評估選擇器時您實際使用的原子或選擇器,動態確定相依性。依照前一次相依性的值,您可能會動態使用其他相依性。彈簧會自動更新目前資料流程圖,使選擇器僅訂閱來自目前相依性集合的更新
在這個範例中,mySelector
會根據 toggleState
原子以及 selectorA
或 selectorB
(取決於 toggleState
的狀態)而定。
const toggleState = atom({key: 'Toggle', default: false});
const mySelector = selector({
key: 'MySelector',
get: ({get}) => {
const toggle = get(toggleState);
if (toggle) {
return get(selectorA);
} else {
return get(selectorB);
}
},
});
可寫入選擇器
雙向選擇器會將輸入值接收為參數,並可以使用它將變更順著資料流程圖向上傳遞。由於使用者可能會設定一個新值或重設選擇器,因此輸入值將是選擇器所代表的類型,或是表示重設動作的 DefaultValue
物件。
這個簡單的選擇器主要用於包裝原子以新增額外欄位。它只是將設定和重設動作傳遞至上游原子。
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
這個選擇器會轉換資料,所以它需要檢查輸入值是否為 DefaultValue
。
const transformSelector = selector({
key: 'TransformSelector',
get: ({get}) => get(myAtom) * 100,
set: ({set}, newValue) =>
set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
非同步選擇器
選擇器也可能有非同步評估函式,並將 Promise
傳回至輸出值。如需詳細資訊,請參閱 這份指南。
const myQuery = selector({
key: 'MyQuery',
get: async ({get}) => {
return await myAsyncQuery(get(queryParamState));
}
});
範例(同步)
import {atom, selector, useRecoilState, DefaultValue, useResetRecoilState} from 'recoil';
const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});
const tempCelsius = selector({
key: 'tempCelsius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) =>
set(
tempFahrenheit,
newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32
),
});
function TempCelsius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);
const resetTemp = useResetRecoilState(tempCelsius);
const addTenCelsius = () => setTempC(tempC + 10);
const addTenFahrenheit = () => setTempF(tempF + 10);
const reset = () => resetTemp();
return (
<div>
Temp (Celsius): {tempC}
<br />
Temp (Fahrenheit): {tempF}
<br />
<button onClick={addTenCelsius}>Add 10 Celsius</button>
<br />
<button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
<br />
<button onClick={reset}>Reset</button>
</div>
);
}
範例(非同步)
import {selector, useRecoilValue} from 'recoil';
const myQuery = selector({
key: 'MyDBQuery',
get: async () => {
const response = await fetch(getMyRequestUrl());
return response.json();
},
});
function QueryResults() {
const queryResults = useRecoilValue(myQuery);
return (
<div>
{queryResults.foo}
</div>
);
}
function ResultsSection() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<QueryResults />
</React.Suspense>
);
}
如需更多複雜的範例,請參閱 這份指南。
傳回包含 callback 的物件
有時候,選擇器可傳回包含 callback 的物件。讓這些 callback 能存取 Recoil 狀態可能會很有用,例如查詢預選值或點擊處理常式。下列範例使用選擇器產生包含點擊處理常式的選單項目;該處理常式會存取 Recoil 狀態。在將這些物件傳遞至 React 組件上下文以外的架構或邏輯時,這可能會很有用。
這個回呼函式和使用 useRecoilCallback()
之間具有對稱性。請注意,getCallback()
傳回的回呼函式可以用作非同步回呼函式,以便稍後存取 Recoil 狀態,不應在評量選擇器值本身時呼叫該回呼函式。
const menuItemState = selectorFamily({
key: 'MenuItem',
get: itemID => ({get, getCallback}) => {
const name = get(itemNameQuery(itemID));
const onClick = getCallback(({snapshot}) => async () => {
const info = await snapshot.getPromise(itemInfoQuery(itemID));
displayInfoModal(info);
});
return {
title: `Show info for ${name}`,
onClick,
};
},
});
能變異狀態的範例
const menuItemState = selectorFamily({
key: 'MenuItem',
get: itemID => ({get, getCallback}) => {
const name = get(itemNameQuery(itemID));
const onClick = getCallback(({refresh}) => () => {
refresh(itemInfoQuery(itemID));
});
return {
title: `Refresh data for ${name}`,
onClick,
};
},
});
快取政策設定
cachePolicy_UNSTABLE
屬性可讓您設定選擇器內部快取的快取行為。這個屬性對於減少在應用程式中有大量選擇器且具備大量變更相依項目的情況下使用的記憶體十分有幫助。目前唯一可設定的選項為 eviction
,但我們可能會在未來新增更多選項。
以下是您可能用來使用此新屬性的範例
const clockState = selector({
key: 'clockState',
get: ({get}) => {
const hour = get(hourState);
const minute = get(minuteState);
const second = get(secondState); // will re-run every second
return `${hour}:${minute}:${second}`;
},
cachePolicy_UNSTABLE: {
// Only store the most recent set of dependencies and their values
eviction: 'most-recent',
},
});
在上面的範例中,clockState
每秒重新計算一次,將一組新的相依項值新增至內部快取,隨著時間推移,內部快取可能會無限增大,從而導致記憶體問題。透過使用 most-recent
清除政策,內部選擇器快取只會保留最新的那組相依項及其值,以及根據這些相依項所產生的實際選擇器值,因而解決記憶體問題。
目前的清除選項為
lru
- 當大小超過maxSize
時,會從快取中清除最近最少使用的值。most-recent
- 只保留最新的值。keep-all
(預設) - 保留快取中的所有項目,且不清除。
備註: 預設清除政策(目前為
keep-all
)可能會在未來變更。