React状态管理 state-management

React状态管理技能涉及使用不同的状态管理库和模式来处理React应用中的本地和全局状态。关键词包括React State, React Context, Zustand, Redux Toolkit, TanStack Query, Jotai。

前端开发 0 次安装 0 次浏览 更新于 3/5/2026

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进行优化