Selector基础 #
什么是 Selector? #
Selector 是一个纯函数,它接受 Atom 或其他 Selector 作为输入,返回一个派生值。当依赖的状态变化时,Selector 会自动重新计算。
text
┌─────────────────────────────────────────────────────┐
│ Selector │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Atom A │────▶│ │ │
│ └─────────┘ │ │ ┌─────────────┐ │
│ │Selector │────▶│ Component │ │
│ ┌─────────┐ │ │ └─────────────┘ │
│ │ Atom B │────▶│ │ │
│ └─────────┘ └─────────┘ │
│ │
│ 依赖变化时自动重新计算 │
│ 结果自动缓存 │
└─────────────────────────────────────────────────────┘
创建 Selector #
基本语法 #
jsx
import { selector } from 'recoil';
const mySelector = selector({
key: 'mySelector',
get: ({ get }) => {
const value = get(someAtom);
return transformedValue;
},
});
简单示例 #
jsx
import { atom, selector, useRecoilValue } from 'recoil';
const firstNameState = atom({
key: 'firstName',
default: 'John',
});
const lastNameState = atom({
key: 'lastName',
default: 'Doe',
});
const fullNameState = selector({
key: 'fullName',
get: ({ get }) => {
const first = get(firstNameState);
const last = get(lastNameState);
return `${first} ${last}`;
},
});
function NameDisplay() {
const fullName = useRecoilValue(fullNameState);
return <div>Full Name: {fullName}</div>;
}
依赖追踪 #
Selector 自动追踪依赖关系:
jsx
const countState = atom({
key: 'count',
default: 0,
});
const doubleCountState = selector({
key: 'doubleCount',
get: ({ get }) => {
const count = get(countState);
return count * 2;
},
});
const tripleCountState = selector({
key: 'tripleCount',
get: ({ get }) => {
const double = get(doubleCountState);
return double * 1.5;
},
});
依赖图:
text
countState ──▶ doubleCountState ──▶ tripleCountState
当 countState 变化时,doubleCountState 和 tripleCountState 都会重新计算。
缓存机制 #
Selector 会缓存计算结果,只有依赖变化时才重新计算:
jsx
const expensiveSelector = selector({
key: 'expensive',
get: ({ get }) => {
const data = get(dataState);
console.log('Computing...');
return data.map(item => {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(item.value);
}
return result;
});
},
});
function Component() {
const result = useRecoilValue(expensiveSelector);
return <div>{result}</div>;
}
实战示例:Todo 过滤 #
jsx
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const todoListState = atom({
key: 'todoListState',
default: [
{ id: 1, text: 'Learn Recoil', completed: true },
{ id: 2, text: 'Build an app', completed: false },
{ id: 3, text: 'Write tests', completed: false },
],
});
const todoFilterState = atom({
key: 'todoFilterState',
default: 'all',
});
const filteredTodoListState = selector({
key: 'filteredTodoListState',
get: ({ get }) => {
const filter = get(todoFilterState);
const list = get(todoListState);
switch (filter) {
case 'completed':
return list.filter(item => item.completed);
case 'active':
return list.filter(item => !item.completed);
default:
return list;
}
},
});
const todoStatsState = selector({
key: 'todoStatsState',
get: ({ get }) => {
const list = get(todoListState);
const total = list.length;
const completed = list.filter(item => item.completed).length;
const active = total - completed;
return { total, completed, active };
},
});
function TodoList() {
const filteredList = useRecoilValue(filteredTodoListState);
return (
<ul>
{filteredList.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
function TodoFilter() {
const [filter, setFilter] = useRecoilState(todoFilterState);
return (
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
);
}
function TodoStats() {
const stats = useRecoilValue(todoStatsState);
return (
<div>
<span>Total: {stats.total}</span>
<span>Active: {stats.active}</span>
<span>Completed: {stats.completed}</span>
</div>
);
}
链式 Selector #
Selector 可以依赖其他 Selector:
jsx
const productsState = atom({
key: 'products',
default: [],
});
const categoryFilterState = atom({
key: 'categoryFilter',
default: 'all',
});
const priceRangeState = atom({
key: 'priceRange',
default: { min: 0, max: Infinity },
});
const filteredByCategoryState = selector({
key: 'filteredByCategory',
get: ({ get }) => {
const products = get(productsState);
const category = get(categoryFilterState);
if (category === 'all') return products;
return products.filter(p => p.category === category);
},
});
const filteredByPriceState = selector({
key: 'filteredByPrice',
get: ({ get }) => {
const products = get(filteredByCategoryState);
const { min, max } = get(priceRangeState);
return products.filter(p => p.price >= min && p.price <= max);
},
});
const sortedProductsState = selector({
key: 'sortedProducts',
get: ({ get }) => {
const products = get(filteredByPriceState);
return [...products].sort((a, b) => a.price - b.price);
},
});
多重依赖 #
jsx
const userState = atom({
key: 'user',
default: { name: 'John', role: 'admin' },
});
const permissionsState = atom({
key: 'permissions',
default: {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read'],
},
});
const userPermissionsState = selector({
key: 'userPermissions',
get: ({ get }) => {
const user = get(userState);
const permissions = get(permissionsState);
return permissions[user.role] || [];
},
});
const canDeleteState = selector({
key: 'canDelete',
get: ({ get }) => {
const userPermissions = get(userPermissionsState);
return userPermissions.includes('delete');
},
});
异步 Selector #
Selector 支持异步操作:
jsx
const userIdState = atom({
key: 'userId',
default: 1,
});
const userQueryState = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(userIdState);
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
function UserProfile() {
const userLoadable = useRecoilValueLoadable(userQueryState);
switch (userLoadable.state) {
case 'loading':
return <div>Loading...</div>;
case 'hasValue':
return <div>{userLoadable.contents.name}</div>;
case 'hasError':
return <div>Error: {userLoadable.contents.message}</div>;
}
}
Selector 配置选项 #
jsx
const mySelector = selector({
key: 'mySelector',
get: ({ get }) => {
return computedValue;
},
set: ({ set }, newValue) => {
set(someAtom, transformedValue);
},
dangerouslyAllowMutability: false,
cachePolicy_UNSTABLE: {
eviction: 'lru',
maxSize: 100,
},
});
| 选项 | 说明 |
|---|---|
key |
唯一标识符 |
get |
计算派生值的函数 |
set |
可选,设置值的函数 |
dangerouslyAllowMutability |
允许直接修改对象 |
cachePolicy_UNSTABLE |
缓存策略 |
性能优化 #
避免不必要的计算 #
jsx
const optimizedSelector = selector({
key: 'optimized',
get: ({ get }) => {
const data = get(dataState);
if (data.length === 0) {
return [];
}
return data.map(expensiveTransform);
},
});
使用缓存策略 #
jsx
const cachedSelector = selector({
key: 'cached',
get: ({ get }) => {
return computeExpensiveValue(get(dataState));
},
cachePolicy_UNSTABLE: {
eviction: 'lru',
maxSize: 10,
},
});
TypeScript 支持 #
tsx
import { selector, useRecoilValue } from 'recoil';
interface TodoStats {
total: number;
completed: number;
active: number;
}
const todoStatsState = selector<TodoStats>({
key: 'todoStatsState',
get: ({ get }) => {
const list = get(todoListState);
const total = list.length;
const completed = list.filter(item => item.completed).length;
return {
total,
completed,
active: total - completed,
};
},
});
function TodoStats() {
const stats = useRecoilValue(todoStatsState);
return <div>{stats.total}</div>;
}
总结 #
Selector 的核心特点:
| 特点 | 说明 |
|---|---|
| 派生状态 | 基于其他状态计算得出 |
| 自动依赖追踪 | 自动检测依赖的状态 |
| 自动缓存 | 相同输入返回缓存结果 |
| 支持异步 | 可以处理异步操作 |
下一步,让我们学习 可写Selector,了解如何实现双向数据流。
最后更新:2026-03-28