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