Jotai 状态管理
在大型项目中使用 Jotai 管理状态的优势是:原子化、组合性强、灵活易维护。以下我们以一个典型的 Todo List
为例,逐步说明如何在大型项目中使用 Jotai 管理全局状态,并给出组织结构和操作方法。
🧠 一、Jotai 优势与适用场景(简述)
在大型项目中,Jotai 适用于以下场景:
- 状态高度模块化(例如:多个列表页、多种筛选条件、分页、用户偏好等)
- 多个组件共享状态,但不想用 Redux 那样繁重的样板代码
- 需要异步、派生状态(Derived state)管理
- 希望按需加载或懒加载部分状态模块
🗂️ 二、项目结构组织建议(模块化)
bash
src/
├── atoms/
│ ├── todos.ts # 所有 todo 的 atoms 和相关状态
│ └── filters.ts # 筛选条件状态
├── hooks/
│ └── useTodoActions.ts # 封装逻辑操作
├── components/
│ ├── TodoList.tsx
│ └── TodoItem.tsx
└── App.tsx
🧩 三、定义状态 atoms(todos.ts)
ts
// atoms/todos.ts
import { atom } from 'jotai'
export type Todo = {
id: number
text: string
completed: boolean
}
// 全部 todos 列表
export const todosAtom = atom<Todo[]>([])
// 派生:未完成 todos
export const incompleteTodosAtom = atom((get) =>
get(todosAtom).filter((t) => !t.completed)
)
🎛️ 四、定义筛选状态(filters.ts)
ts
// atoms/filters.ts
import { atom } from 'jotai'
export type Filter = 'all' | 'completed' | 'incomplete'
export const filterAtom = atom<Filter>('all')
🧮 五、组合派生状态:根据筛选显示 todo(组合 atom)
ts
// atoms/todos.ts 中追加
import { filterAtom } from './filters'
export const filteredTodosAtom = atom((get) => {
const todos = get(todosAtom)
const filter = get(filterAtom)
switch (filter) {
case 'completed':
return todos.filter((t) => t.completed)
case 'incomplete':
return todos.filter((t) => !t.completed)
default:
return todos
}
})
🧰 六、封装操作逻辑(hooks/useTodoActions.ts)
ts
import { useAtom } from 'jotai'
import { todosAtom } from '@/atoms/todos'
let idCounter = 1
export const useTodoActions = () => {
const [todos, setTodos] = useAtom(todosAtom)
const addTodo = (text: string) => {
setTodos([...todos, { id: idCounter++, text, completed: false }])
}
const toggleTodo = (id: number) => {
setTodos(todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t))
}
const deleteTodo = (id: number) => {
setTodos(todos.filter(t => t.id !== id))
}
return { addTodo, toggleTodo, deleteTodo }
}
🧱 七、组件使用示例
TodoList.tsx
tsx
import { useAtomValue } from 'jotai'
import { filteredTodosAtom } from '@/atoms/todos'
import { TodoItem } from './TodoItem'
export const TodoList = () => {
const todos = useAtomValue(filteredTodosAtom)
return (
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
)
}
TodoItem.tsx
tsx
import { Todo } from '@/atoms/todos'
import { useTodoActions } from '@/hooks/useTodoActions'
export const TodoItem = ({ todo }: { todo: Todo }) => {
const { toggleTodo, deleteTodo } = useTodoActions()
return (
<li>
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>🗑</button>
</li>
)
}
⚙️ 八、扩展:异步状态、服务调用(示例)
ts
// 异步获取 todos 示例
import { atom } from 'jotai'
import { atomWithQuery } from 'jotai-tanstack-query' // 或者 atomWithPromise
export const todosAsyncAtom = atomWithQuery(() => ({
queryKey: ['todos'],
queryFn: async () => {
const res = await fetch('/api/todos')
return res.json()
},
}))
✅ 总结:大型项目中使用 Jotai 的技巧
技巧 | 描述 |
---|---|
模块化拆分 atoms | 每个功能拆分单独文件,避免一个大文件 |
使用 atomFamily 管理子项 | 例如每个 todo 独立状态管理 |
封装操作逻辑成 hooks | 类似 useTodoActions ,保持 UI 简洁 |
使用 atomWithStorage 持久化 | 支持本地存储 |
异步状态用 atomWithQuery | 搭配 tanstack-query/React Query 使用 |
使用 Devtools 调试状态 | jotai-devtools 插件帮助调试 |