React中的状态管理模式
1. 何时使用每种解决方案
决策矩阵
| 解决方案 |
最适合 |
复杂性 |
捆绑包大小 |
学习曲线 |
| React State |
组件本地状态 |
低 |
内置 |
简单 |
| React Context |
全局应用状态、主题、认证 |
中 |
内置 |
中等 |
| Zustand |
简单的全局状态、最小样板代码 |
低 |
~1KB |
简单 |
| Redux Toolkit |
复杂应用、时间旅行调试 |
高 |
~10KB |
中等-困难 |
| TanStack Query |
服务器状态、缓存、同步 |
中 |
~13KB |
中等 |
| Jotai |
原子状态、细粒度反应性 |
中 |
~3KB |
中等 |
快速指南
// 使用React useState/useReducer用于:
// - 表单状态
// - UI切换状态
const [isOpen, setIsOpen] = useState(false)
// 使用React Context用于:
// - 主题(暗/亮模式)
// - 认证状态
// - 用户偏好
// 使用Zustand用于:
// - 简单的全局状态
// - 跨组件状态共享
// - 当你想要最小样板代码时
// 使用Redux Toolkit用于:
// - 大型应用
// - 复杂的状态逻辑
// - 当你需要开发工具和中间件时
// 使用TanStack Query用于:
// - 服务器数据获取
// - 缓存和同步
// - 乐观更新
// 使用Jotai用于:
// - 原子状态管理
// - 细粒度反应性
// - 可组合状态
2. React Context
设置
// contexts/ThemeContext.tsx
"use client"
import { createContext, useContext, useState, ReactNode } from "react"
类型主题 = "light" | "dark"
接口ThemeContextValue {
主题: 主题
切换主题: () => void
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light")
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"))
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error("useTheme must be used within ThemeProvider")
}
return context
}
使用模式
// contexts/AuthContext.tsx
"use client"
import { createContext, useContext, useState, ReactNode, useCallback } from "react"
接口用户 {
id: string
name: string
email: string
}
接口AuthContextValue {
用户: 用户 | null
登录: (email: string, password: string) => Promise<void>
登出: () => void
isAuthenticate: boolean
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined)
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<用户 | null>(null)
const login = useCallback(async (email: string, password: string) => {
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
})
const data = await response.json()
setUser(data.user)
}, [])
const logout = useCallback(() => {
setUser(null)
}, [])
return (
<AuthContext.Provider
value={{
用户,
登录,
登出,
isAuthenticate: !!用户,
}}
>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error("useAuth must be used within AuthProvider")
}
return context
}
// 在组件中使用
function UserProfile() {
const { user, logout } = useAuth()
return (
<div>
<h1>欢迎,{user?.name}</h1>
<button onClick={logout}>登出</button>
</div>
)
}
性能考虑
// 问题:Context导致所有消费者重新渲染
const ThemeContext = createContext<ThemeValue | undefined>(undefined)
// 解决方案:分割上下文以防止不必要的重新渲染
const ColorModeContext = createContext<ColorMode | undefined>(undefined)
const FontSizeContext = createContext<FontSize | undefined>(undefined)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [colorMode, setColorMode] = useState<ColorMode>("light")
const [fontSize, setFontSize] = useState<FontSize>("medium")
return (
<ColorModeContext.Provider value={colorMode}>
<FontSizeContext.Provider value={fontSize}>
{children}
</FontSizeContext.Provider>
</ColorModeContext.Provider>
)
}
// 组件只订阅它们使用的
function ColorModeToggle() {
const colorMode = useContext(ColorModeContext)
// 仅在colorMode变化时重新渲染
}
function FontSizeSelector() {
const fontSize = useContext(FontSizeContext)
// 仅在fontSize变化时重新渲染
}
优化的Context与useReducer
// contexts/TodoContext.tsx
"use client"
import { createContext, useContext, useReducer, ReactNode } from "react"
类型待办事项 = {
id: string
文本: string
完成: boolean
}
类型待办事项状态 = {
待办事项: 待办事项[]
过滤器: "all" | "active" | "completed"
}
类型待办事项动作 =
| { 类型: "ADD_TODO"; 文本: string }
| { 类型: "TOGGLE_TODO"; id: string }
| { 类型: "DELETE_TODO"; id: string }
| { 类型: "SET_FILTER"; 过滤器: 待办事项状态["过滤器"] }
函数todoReducer(state: 待办事项状态, action: 待办事项动作): 待办事项状态 {
switch (action.type) {
case "ADD_TODO":
return {
...state,
待办事项: [
...state.待办事项,
{
id: Date.now().toString(),
文本: action.文本,
完成: false,
},
],
}
case "TOGGLE_TODO":
return {
...state,
待办事项: state.待办事项.map((待办事项) =>
待办事项.id === action.id ? { ...待办事项, 完成: !待办事项.完成 } : 待办事项
),
}
case "DELETE_TODO":
return {
...state,
待办事项: state.待办事项.filter((待办事项) => 待办事项.id !== action.id),
}
case "SET_FILTER":
return { ...state, 过滤器: action.过滤器 }
default:
return state
}
}
const TodoContext = createContext<{
状态: 待办事项状态
分派: React.Dispatch<待办事项动作>
} | null>(null)
export function TodoProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(todoReducer, {
待办事项: [],
过滤器: "all",
})
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
)
}
export function useTodos() {
const context = useContext(TodoContext)
if (!context) {
throw new Error("useTodos must be used within TodoProvider")
}
return context
}
3. Zustand
存储创建
// stores/useCounterStore.ts
import { create } from "zustand"
接口CounterState {
计数: number
增加: () => void
减少: () => void
重置: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
计数: 0,
增加: () => set((state) => ({ 计数: state.计数 + 1 })),
减少: () => set((state) => ({ 计数: state.计数 - 1 })),
重置: () => set({ 计数: 0 }),
}))
使用
// components/Counter.tsx
"use client"
import { useCounterStore } from "@/stores/useCounterStore"
export function Counter() {
const { 计数, 增加, 减少, 重置 } = useCounterStore()
return (
<div>
<h1>计数:{计数}</h1>
<button onClick={减少}>-</button>
<button onClick={增加}>+</button>
<button onClick={重置}>重置</button>
</div>
)
}
// 选择性订阅(仅在计数变化时重新渲染)
function CountDisplay() {
const 计数 = useCounterStore((state) => state.计数)
return <span>{计数}</span>
}
// 多个选择器
function CounterControls() {
const 增加 = useCounterStore((state) => state.增加)
const 减少 = useCounterStore((state) => state.减少)
return (
<>
<button onClick={减少}>-</button>
<button onClick={增加}>+</button>
</>
)
}
异步操作
// stores/useUserStore.ts
import { create } from "zustand"
接口用户 {
id: string
name: string
email: string
}
接口用户状态 {
用户: 用户 | null
加载中: boolean
错误: string | null
获取用户: (id: string) => Promise<void>
更新用户: (数据: 部分<用户>) => Promise<void>
}
export const useUserStore = create<用户状态>((set) => ({
用户: null,
加载中: false,
错误: null,
获取用户: 异步 (id: string) => {
set({ 加载中: true, 错误: null })
try {
const response = 等待 fetch(`/api/users/${id}`)
const 用户 = 等待 response.json()
set({ 用户, 加载中: false })
} 捕获 (错误) {
set({ 错误: "Failed to fetch user", 加载中: false })
}
},
更新用户: 异步 (数据: 部分<用户>) => {
set({ 加载中: true, 错误: null })
try {
const response = 等待 fetch(`/api/users/${数据.id}`, {
method: "PATCH",
body: JSON.stringify(数据),
})
const 用户 = 等待 response.json()
set({ 用户, 加载中: false })
} 捕获 (错误) {
set({ 错误: "Failed to update user", 加载中: false })
}
},
}))
中间件
// stores/useStoreWithMiddleware.ts
import { create } from "zustand"
import { devtools, persist } from "zustand/middleware"
接口应用状态 {
计数: number
增加: () => void
}
export const useStoreWithMiddleware = create<应用状态>()(
devtools(
persist(
(set) => ({
计数: 0,
增加: () => set((state) => ({ 计数: state.计数 + 1 })),
}),
{
名称: "app-storage", // localStorage键
部分化: (状态) => ({ 计数: 状态.计数 }), // 仅持久化计数
}
)
)
)
// 自定义中间件用于记录
const logger = (配置) => (set, get, api) => 配置(
(...参数) => {
console.log(" applying", 参数)
set(...参数)
console.log(" new state", get())
},
get,
api
)
export const useLoggedStore = create<应用状态>()(
logger(
devtools((set) => ({
计数: 0,
增加: () => set((state) => ({ 计数: state.计数 + 1 })),
}))
)
)
4. Redux Toolkit
切片
// store/counterSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
接口CounterState {
值: number
}
const initialState: CounterState = {
值: 0,
}
export const counterSlice = createSlice({
名称: "counter",
initialState,
reducers: {
增加: (state) => {
state.值 += 1
},
减少: (state) => {
state.值 -= 1
},
增加ByAmount: (state, action: PayloadAction<number>) => {
state.值 += action.payload
},
重置: (state) => {
state.值 = 0
},
},
})
export const { 增加, 减少, 增加ByAmount, 重置 } = counterSlice.actions
export default counterSlice.reducer
异步Thunks
// store/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"
接口用户 {
id: string
name: string
email: string
}
接口用户状态 {
用户: 用户 | null
加载中: boolean
错误: string | null
}
const initialState: 用户状态 = {
用户: null,
加载中: false,
错误: null,
}
export const fetchUser = createAsyncThunk(
"user/fetchUser",
异步 (userId: string) => {
const response = 等待 fetch(`/api/users/${userId}`)
返回 等待 response.json()
}
)
export const updateUser = createAsyncThunk(
"user/updateUser",
异步 (userData: 部分<用户>) => {
const response = 等待 fetch(`/api/users/${userData.id}`, {
method: "PATCH",
body: JSON.stringify(userData),
})
返回 等待 response.json()
}
)
const userSlice = createSlice({
名称: "user",
initialState,
reducers: {
清除用户: (state) => {
state.用户 = null
},
},
extraReducers: (builder) => {
builder
// 获取用户
.addCase(fetchUser.pending, (state) => {
state.加载中 = true
state.错误 = null
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.加载中 = false
state.用户 = action.payload
})
.addCase(fetchUser.rejected, (state, action) => {
state.加载中 = false
state.错误 = action.error.message || "Failed to fetch user"
})
// 更新用户
.addCase(updateUser.pending, (state) => {
state.加载中 = true
state.错误 = null
})
.addCase(updateUser.fulfilled, (state, action) => {
state.加载中 = false
state.用户 = action.payload
})
.addCase(updateUser.rejected, (state, action) => {
state.加载中 = false
state.错误 = action.error.message || "Failed to update user"
})
},
})
export const { 清除用户 } = userSlice.actions
export default userSlice.reducer
存储配置
// store/index.ts
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./counterSlice"
import userReducer from "./userSlice"
export const store = configureStore({
reducers: {
计数器: counterReducer,
用户: userReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
类型化Hooks
// store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import type { RootState, AppDispatch } from "./store"
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
在组件中使用
// components/Counter.tsx
"use client"
import { useAppDispatch, useAppSelector } from "@/store/hooks"
import { 增加, 减少, 增加ByAmount } from "@/store/counterSlice"
export function Counter() {
const 计数 = useAppSelector((state) => state.计数器.值)
const 分派 = useAppDispatch()
return (
<div>
<h1>计数:{计数}</h1>
<button onClick={() => 分派(减少())}>-</button>
<button onClick={() => 分派(增加())}>+</button>
<button onClick={() => 分派(增加ByAmount(5))}>+5</button>
</div>
)
}
// components/UserProfile.tsx
"use client"
import { useEffect } from "react"
import { useAppDispatch, useAppSelector } from "@/store/hooks"
import { fetchUser, 清除用户 } from "@/store/userSlice"
export function UserProfile({ userId }: { userId: string }) {
const 分派 = useAppDispatch()
const { 用户, 加载中, 错误 } = useAppSelector((state) => state.用户)
useEffect(() => {
分派(fetchUser(userId))
}, [分派, userId])
如果 (加载中) 返回 <div>加载中...</div>
如果 (错误) 返回 <div>错误:{错误}</div>
返回 (
<div>
<h1>{用户?.name}</h1>
<p>{用户?.email}</p>
<button onClick={() => 分派(清除用户())}>清除</button>
</div>
)
}
RTK查询
// store/apiSlice.ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
export const apiSlice = createApi({
reducersPath: "api",
基础查询: fetchBaseQuery({ baseUrl: "/api" }),
标签类型: ["User", "Post"],
端点:(builder) => ({
获取用户: builder.query<用户[], void>({
查询:() => "/users",
提供标签:(result) =>
结果
? [...结果.map(({ id }) => ({ 类型: "User" as const, id })), "User"]
: ["User"],
}),
获取用户: builder.query<用户, string>({
查询:(id) => `/users/${id}`,
提供标签:(_result, _error, id) => [{ 类型: "User", id }],
}),
创建用户: builder.mutation<用户, 部分<用户>>({
查询:(主体) => ({
url: "/users",
method: "POST",
主体,
}),
使标签无效:["User"],
}),
更新用户: builder.mutation<用户, 部分<用户> & { id: string }>({
查询:({ id, ...主体 }) => ({
url: `/users/${id}`,
method: "PATCH",
主体,
}),
使标签无效:(_result, _error, { id }) => [{ 类型: "User", id }],
}),
删除用户: builder.mutation<void, string>({
查询:(id) => ({
url: `/users/${id}`,
method: "DELETE",
}),
使标签无效:(_result, _error, id) => [{ 类型: "User", id }],
}),
}),
})
export const {
useGetUsersQuery,
useGetUserQuery,
useCreateUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
} = apiSlice
// 将api reducer添加到存储中
// store/index.ts
import { configureStore } from "@reduxjs/toolkit"
import { apiSlice } from "./apiSlice"
import counterReducer from "./counterSlice"
export const store = configureStore({
reducers: {
[apiSlice.reducerPath]: apiSlice.reducer,
计数器: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
})
使用RTK查询
// components/UserList.tsx
"use client"
import { useGetUsersQuery, useDeleteUserMutation } from "@/store/apiSlice"
export function UserList() {
const { 数据:用户, 加载中, 错误 } = useGetUsersQuery()
const [删除用户] = useDeleteUserMutation()
如果 (加载中) 返回 <div>加载中...</div>
如果 (错误) 返回 <div>加载用户错误</div>
返回 (
<ul>
{用户?.map((用户) => (
<li key={用户.id}>
{用户.name} - {用户.email}
<button onClick={() => 删除用户(用户.id)}>删除</button>
</li>
))}
</ul>
)
}
5. TanStack查询(React查询)
查询
// hooks/useUsers.ts
import { useQuery } from "@tanstack/react-query"
接口用户 {
id: string
name: string
email: string
}
异步函数 fetchUsers(): Promise<用户[]> {
const response = 等待 fetch("/api/users")
如果 (!response.ok) 抛出新 Error("Failed to fetch users")
返回 等待 response.json()
}
export function useUsers() {
返回 useQuery({
查询键:["users"],
查询函数:fetchUsers,
过时时间:5 * 60 * 1000, // 5分钟
缓存时间:10 * 60 * 1000, // 10分钟
})
}
// 使用
函数 UserList() {
const { 数据:用户, 加载中, 错误, 重新获取 } = useUsers()
如果 (加载中) 返回 <div>加载中...</div>
如果 (错误) 返回 <div>加载用户错误</div>
返回 (
<div>
<button onClick={() => 重新获取()}>刷新</button>
<ul>
{用户?.map((用户) => (
<li key={用户.id}>{用户.name}</li>
))}
</ul>
</div>
)
}
依赖查询
// hooks/useUserTodos.ts
import { useQuery } from "@tanstack/react-query"
接口待办事项 {
id: string
文本: string
完成: boolean
}
异步函数 fetchUserTodos(userId: string): Promise<待办事项[]> {
const response = 等待 fetch(`/api/users/${userId}/todos`)
如果 (!response.ok) 抛出新 Error("Failed to fetch todos")
返回 等待 response.json()
}
export function useUserTodos(userId: string | undefined) {
返回 useQuery({
查询键:["todos", userId],
查询函数:() => fetchUserTodos(userId!),
启用:!!userId, // 仅当userId存在时运行查询
})
}
变异
// hooks/useCreateUser.ts
import { useMutation, useQueryClient } from "@tanstack/react-query"
接口创建用户输入 {
name: string
email: string
}
接口用户 {
id: string
name: string
email: string
}
异步函数 createUser(input: 创建用户输入): Promise<用户> {
const response = 等待 fetch("/api/users", {
method: "POST",
主体:JSON.stringify(input),
})
如果 (!response.ok) 抛出新 Error("Failed to create user")
返回 等待 response.json()
}
export function useCreateUser() {
const queryClient = useQueryClient()
返回 useMutation({
变异函数:createUser,
成功:(newUser) => {
// 使用户列表无效并重新获取
queryClient.invalidateQueries({ 查询键:["users"] })
// 或直接更新缓存(乐观更新)
queryClient.setQueryData(["users"], (旧的:用户[] | undefined) => {
返回旧的 ? [...旧的, newUser] : [newUser]
})
},
错误:(error) => {
console.error("Failed to create user:", error)
},
})
}
// 使用
函数 CreateUserForm() {
const createUser = useCreateUser()
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
createUser.mutate({ name, email })
}
返回 (
<form onSubmit={handleSubmit}>
<input
值={name}
更改={(e) => setName(e.target.value)}
占位符="名称"
/>
<input
值={email}
更改={(e) => setEmail(e.target.value)}
占位符="电子邮件"
/>
<button type="submit" 禁用={createUser.isPending}>
{createUser.isPending ? "Creating..." : "Create User"}
</button>
</form>
)
}
缓存管理
// hooks/useUser.ts
import { useQuery, useQueryClient } from "@tanstack/react-query"
接口用户 {
id: string
name: string
email: string
}
异步函数 fetchUser(id: string): Promise<用户> {
const response = 等待 fetch(`/api/users/${id}`)
如果 (!response.ok) 抛出新 Error("Failed to fetch user")
返回 等待 response.json()
}
export function useUser(id: string) {
const queryClient = useQueryClient()
const查询 = useQuery({
查询键:["user", id],
查询函数:() => fetchUser(id),
过时时间:5 * 60 * 1000,
})
// 预获取相关数据
const prefetchUserTodos = () => {
queryClient.prefetchQuery({
查询键:["todos", id],
查询函数:() => fetch(`/api/users/${id}/todos`).then(r => r.json()),
})
}
// 使相关查询无效
const invalidateUserQueries = () => {
queryClient.invalidateQueries({ 查询键:["user", id] })
queryClient.invalidateQueries({ 查询键:["todos", id] })
}
返回 { ...查询, prefetchUserTodos, invalidateUserQueries }
}
乐观更新
// hooks/useUpdateTodo.ts
import { useMutation, useQueryClient } from "@tanstack/react-query"
接口待办事项 {
id: string
文本: string
完成: boolean
}
异步函数 updateTodo(todo: 待办事项): Promise<待办事项> {
const response = 等待 fetch(`/api/todos/${todo.id}`, {
method: "PATCH",
主体:JSON.stringify(todo),
})
如果 (!response.ok) 抛出新 Error("Failed to update todo")
返回 等待 response.json()
}
export function useUpdateTodo() {
const queryClient = useQueryClient()
返回 useMutation({
变异函数:updateTodo,
onMutate: 异步 (newTodo) => {
// 取消外出重新获取
等待 queryClient.cancelQueries({ 查询键:["todos"] })
// 快照先前值
const previousTodos = queryClient.getQueryData<待办事项[]>(["todos"])
// 乐观更新到新值
queryClient.setQueryData<待办事项[]>(["todos"], (旧的) =>
旧的?.map((待办事项) => (待办事项.id === newTodo.id ? newTodo : 待办事项))
)
// 返回具有先前值的上下文
返回 { previousTodos }
},
错误:(err, newTodo, 上下文) => {
// 回滚到先前值错误
queryClient.setQueryData(["todos"], 上下文?.previousTodos)
},
onSettled: () => {
// 总是重新获取错误或成功后
queryClient.invalidateQueries({ 查询键:["todos"] })
},
})
}
无限查询
// hooks/useInfiniteUsers.ts
import { useInfiniteQuery } from "@tanstack/react-query"
接口用户 {
id: string
name: string
}
接口用户响应 {
用户: 用户[]
下一个游标:string | null
}
异步函数 fetchUsers({ pageParam }: { pageParam?: string }): Promise<用户响应> {
const url = pageParam ? `/api/users?cursor=${pageParam}` : "/api/users"
const response = 等待 fetch(url)
返回 等待 response.json()
}
export function useInfiniteUsers() {
返回 useInfiniteQuery({
查询键:["users", "infinite"],
查询函数:fetchUsers,
初始PageParam:undefined,
获取下一页Param:(lastPage) => lastPage.nextCursor,
})
}
// 使用
函数 InfiniteUserList() {
const {
数据,
加载中,
获取下一页,
还有一页,
获取下一页,
} = useInfiniteUsers()
const 用户 = 数据?.pages.flatMap((page) => page.用户) || []
返回 (
<div>
<ul>
{用户.map((用户) => (
<li key={用户.id}>{用户.name}</li>
))}
</ul>
<button
onClick={() => 获取下一页()}
禁用={!还有一页 || 获取下一页}
>
{获取下一页 ? "Loading more..." : "Load more"}
</button>
</div>
)
}
6. Jotai(原子)
基本原子
// atoms.ts
import { atom } from "jotai"
// 原始原子
export const countAtom = atom(0)
// 只读原子
export const doubledCountAtom = atom((get) => get(countAtom) * 2)
// 仅写入原子
export const incrementAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) + 1)
})
// 读写原子
export const textAtom = atom("hello")
export const uppercaseTextAtom = atom(
(get) => get(textAtom).toUpperCase(),
(get, set, newValue) => {
set(textAtom, newValue.toLowerCase())
}
)
使用
// components/Counter.tsx
"use client"
import { useAtom, useAtomValue, useSetAtom } from "jotai/react"
import { countAtom, doubledCountAtom, incrementAtom } from "@/atoms"
export function Counter() {
const [计数, 设置计数] = useAtom(countAtom)
const 双倍 = useAtomValue(doubledCountAtom)
const 增加 = useSetAtom(incrementAtom)
返回 (
<div>
<h1>计数:{计数}</h1>
<h2>双倍:{双倍}</h2>
<button onClick={() => 设置计数((c) => c + 1)}>+</button>
<button onClick={() => 设置计数((c) => c - 1)}>-</button>
<button onClick={增加}>通过原子增加</button>
</div>
)
}
异步原子
// atoms/user.ts
import { atom } from "jotai"
接口用户 {
id: string
name: string
email: string
}
// 用户ID的基础原子
export const userIdAtom = atom<string | null>(null)
// 异步原子以获取用户
export const userAtom = atom(async (get) => {
const id = get(userIdAtom)
如果 (!id) 返回 null
const response = 等待 fetch(`/api/users/${id}`)
如果 (!response.ok) 抛出新 Error("Failed to fetch user")
返回 等待 response.json()
})
// 带有加载状态的派生原子
export const userWithLoadingAtom = atom((get) => {
const userPromise = get(userAtom)
返回 {
用户:null,
加载中:true,
错误:null,
}
})
// 使用单独原子的更好方法
export const userLoadingAtom = atom(false)
export const userErrorAtom = atom<string | null>(null)
export const userDataAtom = atom<用户 | null>(null)
// 组合原子
export const userStateAtom = atom(
(get) => ({
数据:get(userDataAtom),
加载中:get(userLoadingAtom),
错误:get(userErrorAtom),
}),
异步 (get, set, id: string) => {
set(userLoadingAtom, true)
set(userErrorAtom, null)
尝试 {
const response = 等待 fetch(`/api/users/${id}`)
const 用户 = 等待 response.json()
set(userDataAtom, 用户)
} 捕获 (错误) {
set(userErrorAtom, (错误 as Error).message)
} 最后 {
set(userLoadingAtom, false)
}
}
)
原子家族
// atoms/todos.ts
import { atom } from "jotai"
接口待办事项 {
id: string
文本: string
完成: boolean
}
// 单独待办事项的原子家族
export const todoAtomFamily = atom((id: string) => ({
id,
文本:"",
完成:false,
}))
// 所有待办事项ID的原子
export const todoIdsAtom = atom<string[]>([])
// 所有待办事项的派生原子
export const todosAtom = atom((get) => {
const ids = get(todoIdsAtom)
返回 ids.map((id) => get(todoAtomFamily(id)))
})
// 操作
export const addTodoAtom = atom(null, (get, set, 文本: string) => {
const id = Date.now().toString()
set(todoIdsAtom, (ids) => [...ids, id])
set(todoAtomFamily(id), { id, 文本, 完成:false })
})
export const toggleTodoAtom = atom(null, (get, set, id: string) => {
const 待办事项 = get(todoAtomFamily(id))
set(todoAtomFamily(id), { ...待办事项, 完成:!待办事项.完成 })
})
export const removeTodoAtom = atom(null, (get, set, id: string) => {
set(todoIdsAtom, (ids) => ids.filter((i) => i !== id))
})
7. 决策矩阵
选择正确的解决方案
// 简单的本地状态
函数 Component() {
const [isOpen, setIsOpen] = useState(false)
// 用于:UI切换,表单输入,组件特定状态
}
// 最小设置的全局状态
函数 Component() {
const { 计数, 增加 } = useCounterStore()
// 用于Zustand:简单的全局状态,跨组件共享
}
// 具有开发工具的复杂状态
函数 Component() {
const 计数 = useAppSelector((state) => state.counter.value)
const 分派 = useAppDispatch()
// 用于Redux Toolkit:大型应用,时间旅行,复杂状态逻辑
}
// 具有缓存的服务器状态
函数 Component() {
const { 数据, 加载中 } = useUsers()
// 用于TanStack Query:API调用,缓存,同步
}
// 原子状态
函数 Component() {
const [计数, 设置计数] = useAtom(countAtom)
// 用于Jotai:细粒度反应性,可组合状态
}
迁移指南
// 从useState到Zustand
// 之前
const [count, setCount] = useState(0)
// 之后
const count = useCounterStore((state) => state.count)
const setCount = useCounterStore((state) => state.setCount)
// 从Context到Zustand
// 之前
const { user, login } = useAuth()
// 之后
const user = useAuthStore((state) => state.user)
const login = useAuthStore((state) => state.login)
// 从useState到TanStack Query
// 之前
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetchUsers().then(data => {
setUsers(data)
setLoading(false)
})
}, [])
// 之后
const { 数据:用户, 加载中 } = useUsers()
8. 每种解决方案的最佳实践
React Context
- 分割上下文以防止不必要的重新渲染
- 使用memo进行昂贵的计算
- 考虑使用useReducer进行复杂状态逻辑
- 保持上下文值尽可能稳定
Zustand
- 使用选择性订阅以防止重新渲染
- 将相关状态组合在一个存储中
- 使用中间件进行日志记录、持久性和开发工具
- 保持操作简单专注
Redux Toolkit
- 使用RTK Query进行服务器状态
- 保持切片专注和小巧
- 使用类型化Hooks进行类型安全
- 利用createAsyncThunk进行异步操作
TanStack Query
- 适当使用staleTime和cacheTime
- 实施乐观更新以获得更好的用户体验
- 一致地使用查询键
- 使用错误边界优雅地处理错误
Jotai
- 保持原子小而专注
- 使用原子家族进行集合
- 利用派生原子进行计算值
- 使用useAtomValue和useSetAtom进行优化