项目结构 #
推荐目录结构 #
text
src/
├── atoms/ # Atom 定义
│ ├── index.js # 统一导出
│ ├── userAtoms.js # 用户相关 Atom
│ ├── todoAtoms.js # Todo 相关 Atom
│ └── uiAtoms.js # UI 状态 Atom
├── selectors/ # Selector 定义
│ ├── index.js # 统一导出
│ ├── userSelectors.js # 用户相关 Selector
│ └── todoSelectors.js # Todo 相关 Selector
├── hooks/ # 自定义 Hooks
│ ├── useUser.js # 用户相关 Hook
│ ├── useTodo.js # Todo 相关 Hook
│ └── useAuth.js # 认证相关 Hook
├── components/ # React 组件
│ ├── common/ # 通用组件
│ ├── user/ # 用户相关组件
│ └── todo/ # Todo 相关组件
├── effects/ # Atom Effects
│ ├── localStorage.js # 本地存储 Effect
│ └── sync.js # 同步 Effect
├── utils/ # 工具函数
│ └── helpers.js
└── App.jsx # 根组件
按功能模块组织 #
text
src/
├── modules/
│ ├── user/ # 用户模块
│ │ ├── atoms.js # 用户 Atom
│ │ ├── selectors.js # 用户 Selector
│ │ ├── hooks.js # 用户 Hook
│ │ ├── components/ # 用户组件
│ │ └── index.js # 模块导出
│ ├── todo/ # Todo 模块
│ │ ├── atoms.js
│ │ ├── selectors.js
│ │ ├── hooks.js
│ │ ├── components/
│ │ └── index.js
│ └── ui/ # UI 模块
│ ├── atoms.js
│ ├── selectors.js
│ └── index.js
├── common/ # 公共资源
│ ├── components/
│ ├── hooks/
│ └── utils/
└── App.jsx
Atom 文件组织 #
单一职责 #
jsx
const userState = atom({
key: 'user',
default: null,
});
const userSettingsState = atom({
key: 'userSettings',
default: {
theme: 'light',
language: 'en',
},
});
按领域分组 #
jsx
const userAtoms = {
userState: atom({
key: 'user',
default: null,
}),
userSettingsState: atom({
key: 'userSettings',
default: { theme: 'light' },
}),
userPreferencesState: atom({
key: 'userPreferences',
default: {},
}),
};
export default userAtoms;
统一导出 #
jsx
export { default as userAtoms } from './userAtoms';
export { default as todoAtoms } from './todoAtoms';
export { default as uiAtoms } from './uiAtoms';
Selector 文件组织 #
派生状态 #
jsx
import { selector } from 'recoil';
import { todoListState } from './todoAtoms';
export const todoStatsState = selector({
key: 'todoStats',
get: ({ get }) => {
const list = get(todoListState);
return {
total: list.length,
completed: list.filter(t => t.completed).length,
};
},
});
export const filteredTodoListState = selector({
key: 'filteredTodoList',
get: ({ get }) => {
const list = get(todoListState);
const filter = get(todoFilterState);
switch (filter) {
case 'completed':
return list.filter(t => t.completed);
case 'active':
return list.filter(t => !t.completed);
default:
return list;
}
},
});
自定义 Hooks #
封装业务逻辑 #
jsx
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { todoListState, todoFilterState } from '../atoms/todoAtoms';
export function useTodo() {
const [todos, setTodos] = useRecoilState(todoListState);
const [filter, setFilter] = useRecoilState(todoFilterState);
const addTodo = (text) => {
setTodos(prev => [
...prev,
{ id: Date.now(), text, completed: false },
]);
};
const toggleTodo = (id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
const clearCompleted = () => {
setTodos(prev => prev.filter(todo => !todo.completed));
};
return {
todos,
filter,
setFilter,
addTodo,
toggleTodo,
deleteTodo,
clearCompleted,
};
}
组合多个状态 #
jsx
export function useUser() {
const [user, setUser] = useRecoilState(userState);
const settings = useRecoilValue(userSettingsState);
const setSettings = useSetRecoilState(userSettingsState);
const login = async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
const data = await response.json();
setUser(data.user);
};
const logout = () => {
setUser(null);
};
const updateSettings = (newSettings) => {
setSettings(prev => ({ ...prev, ...newSettings }));
};
return {
user,
settings,
login,
logout,
updateSettings,
isLoggedIn: !!user,
};
}
Effects 组织 #
可复用 Effect #
jsx
export const localStorageEffect = (key) => ({ setSelf, onSet }) => {
const savedValue = localStorage.getItem(key);
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
}
onSet((newValue, _, isReset) => {
isReset
? localStorage.removeItem(key)
: localStorage.setItem(key, JSON.stringify(newValue));
});
};
export const sessionStorageEffect = (key) => ({ setSelf, onSet }) => {
const savedValue = sessionStorage.getItem(key);
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
}
onSet((newValue, _, isReset) => {
isReset
? sessionStorage.removeItem(key)
: sessionStorage.setItem(key, JSON.stringify(newValue));
});
};
使用 Effect #
jsx
import { localStorageEffect } from '../effects/localStorage';
const userSettingsState = atom({
key: 'userSettings',
default: { theme: 'light' },
effects: [localStorageEffect('user_settings')],
});
命名规范 #
Atom 命名 #
jsx
const userState = atom({ key: 'userState', default: null });
const todoListState = atom({ key: 'todoListState', default: [] });
const isLoggedInState = atom({ key: 'isLoggedInState', default: false });
Selector 命名 #
jsx
const filteredTodoListState = selector({ key: 'filteredTodoListState', ... });
const todoStatsState = selector({ key: 'todoStatsState', ... });
const userNameState = selector({ key: 'userNameState', ... });
atomFamily 命名 #
jsx
const todoFamily = atomFamily({ key: 'todoFamily', ... });
const userQueryFamily = atomFamily({ key: 'userQueryFamily', ... });
selectorFamily 命名 #
jsx
const userQueryState = selectorFamily({ key: 'userQueryState', ... });
const searchResultsState = selectorFamily({ key: 'searchResultsState', ... });
完整示例 #
text
src/
├── modules/
│ ├── auth/
│ │ ├── atoms.js
│ │ ├── selectors.js
│ │ ├── hooks.js
│ │ ├── components/
│ │ │ ├── LoginForm.jsx
│ │ │ └── LogoutButton.jsx
│ │ └── index.js
│ ├── todo/
│ │ ├── atoms.js
│ │ ├── selectors.js
│ │ ├── hooks.js
│ │ ├── components/
│ │ │ ├── TodoList.jsx
│ │ │ ├── TodoItem.jsx
│ │ │ └── TodoInput.jsx
│ │ └── index.js
│ └── ui/
│ ├── atoms.js
│ └── components/
│ ├── ThemeToggle.jsx
│ └── Modal.jsx
├── effects/
│ └── storage.js
├── common/
│ ├── components/
│ │ ├── Button.jsx
│ │ └── Input.jsx
│ └── hooks/
│ └── useDebounce.js
└── App.jsx
总结 #
项目结构的核心原则:
| 原则 | 说明 |
|---|---|
| 按功能分组 | 相关代码放在一起 |
| 单一职责 | 每个 Atom/Selector 只负责一件事 |
| 统一命名 | 使用一致的命名规范 |
| 模块导出 | 使用 index.js 统一导出 |
下一步,让我们学习 TypeScript集成,了解 Recoil 与 TypeScript 的集成。
最后更新:2026-03-28