跳至主要内容

selector(options)

选择器 在 Recoil 中代表一个函数或派生状态。你可以将其视为类似于一个“幂等”或“纯函数”,没有副作用,对于给定的依赖值集始终返回相同的值。如果只提供了一个 get 函数,则选择器是只读的,并返回一个 RecoilValueReadOnly 对象。如果也提供了 set,则它将返回一个可写 RecoilState 对象。

Recoil 管理 atom 和选择器状态变化,以了解何时通知订阅该选择器的组件重新渲染。如果直接修改选择器对象的值,它可能会绕过此操作并避免正确通知订阅的组件。为了帮助检测错误,Recoil 将在开发模式下冻结选择器值对象。


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 - 用于在内部识别选择器的唯一字符串。此字符串应与整个应用程序中其他 atom 和选择器相区别。如果用于持久化,则需要在执行之间保持稳定。
  • get - 用于评估派生状态值的函数。它可以返回一个值,一个异步 Promise,一个 Loadable,或另一个表示相同类型的 atom 或选择器。要直接将选择器值设置为 PromiseLoadable 之类的内容,可以使用 selector.value(...) 将其包装起来。此回调作为第一个参数传递一个对象,其中包含以下属性
    • get() - 用于从其他 atom/选择器中检索值的函数。传递给此函数的所有 atom/选择器将隐式添加到选择器的依赖项列表中。如果选择器的任何依赖项发生变化,选择器将重新评估。
    • getCallback() - 用于创建具有 回调接口 的 Recoil 感知回调的函数。参见下面的 示例
  • set? - 如果设置了此属性,则选择器将返回可写状态。传递给第一个参数的回调对象和新的传入值。传入值可以是类型为 T 的值,也可以是类型为 DefaultValue 的对象,如果用户重置了选择器。回调包括
    • get() - 用于从其他 atom/选择器中检索值的函数。此函数不会将选择器订阅到给定的 atom/选择器。
    • set() - 用于设置上游 Recoil 状态值的函数。第一个参数是 Recoil 状态,第二个参数是新值。新值可以是更新器函数或 DefaultValue 对象,以传播重置操作。
    • reset() - 用于重置为上游 Recoil 状态的默认值的函数。唯一的参数是 Recoil 状态。
  • dangerouslyAllowMutability - 在某些情况下,可能需要允许修改存储在选择器中的对象,而这些对象不代表状态更改。使用此选项可以覆盖在开发模式下冻结对象。
  • cachePolicy_UNSTABLE - 定义内部选择器缓存的行为。对于控制在具有大量更改依赖项的选择器的应用程序中的内存占用量很有用。
    • eviction - 可以设置为 lru(需要设置 maxSize)、keep-all(默认)或 most-recent。当缓存的大小超过 maxSize 时,lru 缓存将从选择器缓存中逐出使用最少的最近的值。keep-all 策略意味着所有选择器依赖项及其值将无限期地存储在选择器缓存中。most-recent 策略将使用大小为 1 的缓存,并将仅保留最近保存的依赖项集及其值。
    • 请注意,缓存根据包含所有依赖项及其值的键存储选择器值。这意味着内部选择器缓存的大小取决于选择器值的大小以及所有依赖项的唯一值的数量。
    • 请注意,默认逐出策略(当前为 keep-all)将来可能会更改。

具有简单静态依赖项的选择器

const mySelector = selector({
key: 'MySelector',
get: ({get}) => get(myAtom) * 100,
});

动态依赖项

只读选择器具有一个 get 方法,该方法根据依赖项评估选择器的值。如果这些依赖项中的任何一个更新,则选择器将重新评估。依赖项是根据在评估选择器时实际使用的 atom 或选择器动态确定的。根据先前依赖项的值,你可能会动态使用不同的附加依赖项。Recoil 将自动更新当前数据流图,以便选择器只订阅来自当前依赖项集的更新

在此示例中,mySelector 将依赖于 toggleState atom 以及 selectorAselectorB,具体取决于 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 对象。

此简单选择器本质上包装了一个 atom 以添加一个额外的字段。它只是将设置和重置操作传递到上游 atom。

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>
);
}

有关更复杂的示例,请参阅 本指南

返回带有回调的对象

有时选择器可用于返回包含回调的对象。对于这些回调访问 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)将来可能会更改。