Atom家族 #

什么是 atomFamily? #

atomFamily 是一个工厂函数,用于根据参数动态创建多个相关的 Atom。当你需要为每个实体(如用户、产品、Todo 等)维护独立的状态时,atomFamily 非常有用。

text
┌─────────────────────────────────────────────────────┐
│                   atomFamily                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│   atomFamily(param) ──▶ Atom                        │
│                                                     │
│   ┌─────────────────────────────────────────────┐   │
│   │  todoFamily(1) ──▶ todoAtom_1               │   │
│   │  todoFamily(2) ──▶ todoAtom_2               │   │
│   │  todoFamily(3) ──▶ todoAtom_3               │   │
│   └─────────────────────────────────────────────┘   │
│                                                     │
└─────────────────────────────────────────────────────┘

基本语法 #

jsx
import { atomFamily } from 'recoil';

const myFamily = atomFamily({
  key: 'myFamily',
  default: (param) => defaultValue,
});

创建 atomFamily #

简单示例 #

jsx
import { atomFamily, useRecoilState } from 'recoil';

const countFamily = atomFamily({
  key: 'countFamily',
  default: 0,
});

function Counter({ id }) {
  const [count, setCount] = useRecoilState(countFamily(id));
  
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

function App() {
  return (
    <div>
      <Counter id="counter-1" />
      <Counter id="counter-2" />
      <Counter id="counter-3" />
    </div>
  );
}

参数化默认值 #

jsx
const userFamily = atomFamily({
  key: 'userFamily',
  default: (userId) => ({
    id: userId,
    name: '',
    email: '',
    loading: false,
  }),
});

function UserProfile({ userId }) {
  const [user, setUser] = useRecoilState(userFamily(userId));
  
  return (
    <div>
      <p>ID: {user.id}</p>
      <input
        value={user.name}
        onChange={(e) => setUser({ ...user, name: e.target.value })}
      />
    </div>
  );
}

实战示例:Todo 列表 #

jsx
import { atom, atomFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

const todoIdsState = atom({
  key: 'todoIdsState',
  default: [],
});

const todoFamily = atomFamily({
  key: 'todoFamily',
  default: (id) => ({
    id,
    text: '',
    completed: false,
    createdAt: Date.now(),
  }),
});

function TodoItem({ id }) {
  const [todo, setTodo] = useRecoilState(todoFamily(id));
  const setTodoIds = useSetRecoilState(todoIdsState);
  
  const toggleComplete = () => {
    setTodo({ ...todo, completed: !todo.completed });
  };
  
  const deleteTodo = () => {
    setTodoIds(ids => ids.filter(i => i !== id));
  };
  
  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={toggleComplete}
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <button onClick={deleteTodo}>Delete</button>
    </div>
  );
}

function TodoList() {
  const [todoIds, setTodoIds] = useRecoilState(todoIdsState);
  
  const addTodo = () => {
    const newId = Date.now();
    setTodoIds([...todoIds, newId]);
  };
  
  return (
    <div>
      <button onClick={addTodo}>Add Todo</button>
      {todoIds.map(id => (
        <TodoItem key={id} id={id} />
      ))}
    </div>
  );
}

atomFamily 配置 #

完整配置 #

jsx
const myFamily = atomFamily({
  key: 'myFamily',
  default: (param) => defaultValue,
  dangerouslyAllowMutability: false,
  effects: (param) => [effect1, effect2],
});

Effects 配置 #

jsx
const localStorageEffect = (key) => ({ setSelf, onSet }) => {
  const savedValue = localStorage.getItem(key);
  if (savedValue != null) {
    setSelf(JSON.parse(savedValue));
  }
  
  onSet((newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue));
  });
};

const todoFamily = atomFamily({
  key: 'todoFamily',
  default: (id) => ({ id, text: '', completed: false }),
  effects: (id) => [
    localStorageEffect(`todo_${id}`),
  ],
});

参数类型 #

字符串参数 #

jsx
const userFamily = atomFamily({
  key: 'userFamily',
  default: (username) => ({ username, profile: null }),
});

function UserCard({ username }) {
  const [user] = useRecoilState(userFamily(username));
  return <div>{user.username}</div>;
}

数字参数 #

jsx
const productFamily = atomFamily({
  key: 'productFamily',
  default: (productId) => ({ id: productId, name: '', price: 0 }),
});

function Product({ productId }) {
  const [product, setProduct] = useRecoilState(productFamily(productId));
  return <div>{product.name}</div>;
}

对象参数 #

jsx
const filterFamily = atomFamily({
  key: 'filterFamily',
  default: ({ category, status }) => ({
    category,
    status,
    sortBy: 'date',
  }),
});

function FilterPanel({ category, status }) {
  const [filter, setFilter] = useRecoilState(filterFamily({ category, status }));
  return <div>{filter.category}</div>;
}

参数缓存 #

atomFamily 会缓存相同参数创建的 Atom:

jsx
const countFamily = atomFamily({
  key: 'countFamily',
  default: 0,
});

function Component() {
  const [count1] = useRecoilState(countFamily('a'));
  const [count2] = useRecoilState(countFamily('a'));
  
  console.log(count1 === count2);
  
  return null;
}

自定义缓存键 #

默认情况下,atomFamily 使用参数的引用相等性来缓存。对于对象参数,可以使用 cachePolicyForParams_UNSTABLE

jsx
const filterFamily = atomFamily({
  key: 'filterFamily',
  default: (params) => params,
  cachePolicyForParams_UNSTABLE: {
    equality: 'value',
  },
});

与 Selector 结合 #

jsx
import { selectorFamily } from 'recoil';

const todoFamily = atomFamily({
  key: 'todoFamily',
  default: (id) => ({ id, text: '', completed: false }),
});

const completedTodoIdsState = selector({
  key: 'completedTodoIdsState',
  get: ({ get }) => {
    const ids = get(todoIdsState);
    return ids.filter(id => get(todoFamily(id)).completed);
  },
});

异步默认值 #

jsx
const userFamily = atomFamily({
  key: 'userFamily',
  default: async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
});

function UserProfile({ userId }) {
  const userLoadable = useRecoilValueLoadable(userFamily(userId));
  
  switch (userLoadable.state) {
    case 'loading':
      return <div>Loading...</div>;
    case 'hasValue':
      return <div>{userLoadable.contents.name}</div>;
    case 'hasError':
      return <div>Error loading user</div>;
  }
}

完整示例:多标签编辑器 #

jsx
import { atom, atomFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

const activeTabState = atom({
  key: 'activeTabState',
  default: 'tab-1',
});

const tabIdsState = atom({
  key: 'tabIdsState',
  default: ['tab-1'],
});

const tabFamily = atomFamily({
  key: 'tabFamily',
  default: (tabId) => ({
    id: tabId,
    title: `Tab ${tabId}`,
    content: '',
    savedAt: null,
  }),
  effects: (tabId) => [
    ({ onSet }) => {
      onSet((newValue, oldValue) => {
        console.log(`Tab ${tabId} updated:`, { oldValue, newValue });
      });
    },
  ],
});

function Tab({ tabId }) {
  const [tab, setTab] = useRecoilState(tabFamily(tabId));
  const isActive = useRecoilValue(activeTabState) === tabId;
  const setActiveTab = useSetRecoilState(activeTabState);
  
  const handleContentChange = (e) => {
    setTab({ ...tab, content: e.target.value });
  };
  
  if (!isActive) return null;
  
  return (
    <div className="tab-content">
      <input
        value={tab.title}
        onChange={(e) => setTab({ ...tab, title: e.target.value })}
      />
      <textarea
        value={tab.content}
        onChange={handleContentChange}
      />
    </div>
  );
}

function TabList() {
  const tabIds = useRecoilValue(tabIdsState);
  const activeTab = useRecoilValue(activeTabState);
  const setActiveTab = useSetRecoilState(activeTabState);
  const setTabIds = useSetRecoilState(tabIdsState);
  
  const addTab = () => {
    const newId = `tab-${Date.now()}`;
    setTabIds([...tabIds, newId]);
    setActiveTab(newId);
  };
  
  const closeTab = (tabId) => {
    setTabIds(ids => ids.filter(id => id !== tabId));
    if (activeTab === tabId) {
      setActiveTab(tabIds[0]);
    }
  };
  
  return (
    <div>
      <div className="tab-header">
        {tabIds.map(id => (
          <div
            key={id}
            className={activeTab === id ? 'active' : ''}
            onClick={() => setActiveTab(id)}
          >
            Tab {id}
            <button onClick={(e) => { e.stopPropagation(); closeTab(id); }}>×</button>
          </div>
        ))}
        <button onClick={addTab}>+</button>
      </div>
      {tabIds.map(id => (
        <Tab key={id} tabId={id} />
      ))}
    </div>
  );
}

TypeScript 支持 #

tsx
import { atomFamily, useRecoilState } from 'recoil';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

const todoFamily = atomFamily<Todo, number>({
  key: 'todoFamily',
  default: (id) => ({
    id,
    text: '',
    completed: false,
  }),
});

function TodoItem({ id }: { id: number }) {
  const [todo, setTodo] = useRecoilState(todoFamily(id));
  
  return (
    <div>
      <input
        value={todo.text}
        onChange={(e) => setTodo({ ...todo, text: e.target.value })}
      />
    </div>
  );
}

总结 #

atomFamily 的核心用途:

场景 说明
列表项状态 每个列表项独立的状态
缓存数据 按ID缓存API数据
表单字段 动态表单字段状态
多实例组件 相同组件的多个实例

下一步,让我们学习 Selector基础,了解如何创建派生状态。

最后更新:2026-03-28