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 变化时,doubleCountStatetripleCountState 都会重新计算。

缓存机制 #

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