项目结构 #

推荐目录结构 #

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