React进阶:状态管理选择题

React进阶:状态管理选择题

引言

随着React应用复杂度增加,选择合适的状态管理方案成为我们面临的关键决策。

状态管理本质上是解决"谁来存储数据"以及"如何更新和分发这些数据"的问题。在React生态中,随着应用规模扩大,不同组件间的状态共享和同步变得越来越复杂,这促使了众多状态管理库的诞生。

状态管理方案全景

React内置状态管理

React本身提供了多种内置状态管理机制,包括组件局部状态和Context API。这些原生解决方案是理解其他状态管理库的基础。

useState:组件局部状态

useState钩子提供了最简单的状态管理方式,适用于组件内部状态。它遵循不可变性原则,每次状态更新都会触发组件重新渲染。

jsx 复制代码
function Counter() {
  // 局部组件状态
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}

useState的关键特性是简单直观,但当状态逻辑复杂时会变得难以维护。例如,当多个状态相互依赖时,使用多个useState调用会导致代码变得零散且难以追踪状态变化。

useReducer:复杂状态逻辑

useReducer提供了更结构化的状态管理方式,特别适合处理包含多个子值的复杂状态逻辑。它借鉴了Redux的设计理念,通过分发action来更新状态。

jsx 复制代码
function complexCounter() {
  // 复杂状态逻辑
  const [state, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'increment': return {count: state.count + 1};
      case 'decrement': return {count: state.count - 1};
      case 'reset': return {count: 0};
      case 'set': return {count: action.payload};
      default: return state;
    }
  }, {count: 0});

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'reset'})}>Reset</button>
      <button onClick={() => dispatch({type: 'set', payload: 100})}>Set to 100</button>
    </div>
  );
}

useReducer的优势在于集中管理相关状态逻辑,使状态变化更可预测。然而,它仍然局限于单个组件或通过props传递,无法直接解决跨组件状态共享问题。

Context API:跨组件状态共享

Context API提供了一种在组件树中共享状态的方式,无需通过props层层传递。它是React内置的"依赖注入"系统。

jsx 复制代码
// 创建上下文
const ThemeContext = React.createContext({
  theme: 'light',
  setTheme: () => {}
});

function App() {
  const [theme, setTheme] = useState('light');
  
  // 提供上下文值给整个组件树
  return (
    <ThemeContext.Provider value={{theme, setTheme}}>
      <Header />
      <Main />
      <Footer />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  // 消费上下文,无需通过props传递
  const {theme, setTheme} = useContext(ThemeContext);
  
  return (
    <button 
      style={{background: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff'}}
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      当前主题: {theme} (点击切换)
    </button>
  );
}

Context API解决了props drilling问题,但它本身并不是一个完整的状态管理解决方案。每当Provider的值变化时,所有消费该Context的组件都会重新渲染,这可能导致性能问题。此外,大型应用中使用多个Context会导致组件嵌套过深,降低代码可读性。

适用场景:组件树深度适中,状态共享需求简单的中小型应用。典型用例包括主题切换、用户偏好设置、多语言支持等全局配置。

优势

  • 零依赖,React官方支持,保证长期维护
  • 学习成本低,API简洁,与React组件模型一致
  • 无需额外库,减小打包体积,降低项目复杂度
  • 无需额外配置,可即时使用

局限性

  • Context触发的重渲染优化困难,可能导致性能问题
  • 多Context组合使用导致嵌套地狱,降低代码可读性
  • 缺乏专门的开发者工具支持,调试体验较差
  • 状态变化追踪困难,无法实现时间旅行调试
  • 难以实现状态持久化、中间件等高级功能

Redux生态系统

Redux作为React生态中最成熟的状态管理解决方案,通过单一数据源、不可变数据和纯函数reducer实现可预测的状态管理。现代Redux通过Redux Toolkit(RTK)大幅简化了开发体验。

Redux核心概念

Redux基于三个基本原则:

  1. 单一数据源:整个应用的状态存储在单个store的对象树中
  2. 状态只读:唯一改变状态的方法是触发action
  3. 使用纯函数进行修改:reducer是纯函数,接收先前的状态和action,返回新状态

传统Redux实现需要手动创建action creators、reducers和store,代码量较大:

jsx 复制代码
// 定义action类型
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// 创建action creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// 创建reducer
const counterReducer = (state = { value: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, value: state.value + 1 };
    case DECREMENT:
      return { ...state, value: state.value - 1 };
    default:
      return state;
  }
};

// 创建store
const store = createStore(counterReducer);

// 组件中使用
function Counter() {
  const count = useSelector(state => state.value);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}
Redux Toolkit:现代Redux开发

Redux Toolkit极大简化了Redux开发,减少了样板代码,提供了更现代化的API:

jsx 复制代码
// 创建slice
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => {
      // RTK使用Immer库,允许"直接修改"状态
      // 实际上Immer会在底层确保不可变性
      state.value += 1;
    },
    decrement: state => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

// 导出action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 创建store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

// 组件中使用
function Counter() {
  // 使用选择器获取状态片段
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
    </div>
  );
}

RTK不仅简化了Redux的使用,还内置了多种开发工具和最佳实践,包括Immer(简化不可变更新)、Thunk(处理异步逻辑)和DevTools扩展支持。

异步操作处理

处理API请求等异步操作是状态管理的重要部分。Redux通过中间件处理异步逻辑,RTK提供了createAsyncThunk简化这一过程:

jsx 复制代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 创建异步thunk
export const fetchUserById = createAsyncThunk(
  'users/fetchById',
  async (userId, { rejectWithValue }) => {
    try {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      if (!response.ok) throw new Error('Server Error');
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUserById.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload;
      });
  }
});

// 组件中使用
function UserProfile({ userId }) {
  const dispatch = useDispatch();
  const { data: user, status, error } = useSelector(state => state.user);
  
  useEffect(() => {
    if (status === 'idle') {
      dispatch(fetchUserById(userId));
    }
  }, [userId, status, dispatch]);
  
  if (status === 'loading') return <div>Loading...</div>;
  if (status === 'failed') return <div>Error: {error}</div>;
  if (!user) return null;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

这种模式提供了一致的方式处理异步操作生命周期(开始、成功、失败),使状态管理更加可预测。

适用场景:大型企业级应用,复杂的状态逻辑,需要严格状态追踪的场景,多团队协作的项目。典型用例包括后台管理系统、复杂表单应用、大型电商平台等。

优势

  • 成熟的生态系统,大量中间件和第三方集成
  • 强大的开发者工具,时间旅行调试使问题定位更容易
  • 状态变化可预测,严格的单向数据流提高代码可维护性
  • RTK极大简化了模板代码,提升开发效率
  • 与React Router、React Query等库良好集成
  • 丰富的文档和社区支持,学习资源丰富
  • 适合大型团队协作,状态管理规范统一

局限性

  • 学习曲线相对陡峭,概念较多
  • 即使使用RTK,模板代码量仍高于其他轻量级方案
  • 小型应用可能显得过度设计
  • 引入额外的运行时开销和打包体积
  • 可能引入额外的性能考量,需要手动优化选择器

Zustand:轻量级状态管理

Zustand是一个极简的状态管理库,专注于简化API并保持高性能。它结合了Redux的可预测性和React hooks的简洁性,提供了更现代化的开发体验。

创建和使用Store

Zustand的核心API非常简洁,使用闭包创建store并通过hook访问状态:

jsx 复制代码
import create from 'zustand';

// 创建store
const useStore = create(set => ({
  // 初始状态
  count: 0,
  
  // 更新状态的操作
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  incrementByAmount: (amount) => set(state => ({ count: state.count + amount }))
}));

// 组件中使用
function Counter() {
  // 解构需要的状态和操作
  const { count, increment, decrement, reset, incrementByAmount } = useStore();
  
  // 也可以选择性获取部分状态,优化重渲染
  // const count = useStore(state => state.count);
  // const increment = useStore(state => state.increment);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
      <button onClick={() => incrementByAmount(10)}>+10</button>
    </div>
  );
}

与Redux相比,Zustand省略了action types、reducers和dispatch等概念,直接通过函数更新状态。这种简化的API大大降低了学习成本和代码量。

中间件和持久化

尽管API简单,Zustand仍支持中间件系统,可以扩展store功能:

jsx 复制代码
import create from 'zustand';
import { persist, devtools } from 'zustand/middleware';

// 使用中间件
const useStore = create(
  devtools(                // 开发者工具支持
    persist(               // 状态持久化
      (set, get) => ({
        count: 0,
        increment: () => set(state => ({ count: state.count + 1 })),
        incrementAsync: () => {
          setTimeout(() => {
            // 可以使用get获取当前状态
            set({ count: get().count + 1 });
          }, 1000);
        }
      }),
      {
        name: 'counter-storage', // 持久化存储的键名
        getStorage: () => localStorage, // 使用localStorage
      }
    )
  )
);

这种模块化的中间件设计允许灵活组合不同功能,同时保持API简洁。

状态切片和组合

对于大型应用,Zustand支持将状态分割为多个"切片",然后组合使用:

jsx 复制代码
// 用户相关状态
const createUserSlice = (set) => ({
  user: null,
  loading: false,
  error: null,
  login: async (credentials) => {
    set({ loading: true });
    try {
      const user = await loginApi(credentials);
      set({ user, loading: false, error: null });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
  logout: () => set({ user: null })
});

// 购物车相关状态
const createCartSlice = (set, get) => ({
  items: [],
  totalItems: 0,
  addItem: (item) => {
    set((state) => ({
      items: [...state.items, item],
      totalItems: state.totalItems + 1
    }));
  },
  removeItem: (itemId) => {
    set((state) => ({
      items: state.items.filter(item => item.id !== itemId),
      totalItems: state.totalItems - 1
    }));
  },
  clearCart: () => set({ items: [], totalItems: 0 })
});

// 组合多个状态切片
const useStore = create((set, get) => ({
  ...createUserSlice(set, get),
  ...createCartSlice(set, get)
}));

// 组件中使用
function ShoppingCart() {
  const { items, addItem, removeItem, user } = useStore();
  
  return (
    <div>
      {user ? (
        <>
          <h2>{user.name}的购物车</h2>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                {item.name} - ${item.price}
                <button onClick={() => removeItem(item.id)}>移除</button>
              </li>
            ))}
          </ul>
        </>
      ) : (
        <p>请先登录</p>
      )}
    </div>
  );
}

这种组合方式可以在保持简洁API的同时,实现与Redux类似的代码组织结构。

适用场景:中小型应用,需要全局状态但希望保持简单API的项目,对Redux感到疲惫的团队。典型用例包括交互性应用、单页应用、工具类网站等。

优势

  • 极简API,几乎零配置,减少样板代码
  • 基于hook的直观使用方式,无需Provider包裹
  • 自动优化渲染,仅在使用的状态变化时更新
  • 支持中间件、持久化等扩展功能
  • 体积小(约3KB),学习成本低
  • 类型支持良好,TypeScript开发体验优秀
  • 可以逐步采用,无需重构整个应用

局限性

  • 缺乏时间旅行调试等高级调试功能(虽然有Redux DevTools集成)
  • 大型应用可能需要更严格的状态组织方式
  • 异步操作处理相对基础,缺少内置的异步状态管理模式
  • 不支持状态间复杂依赖关系的自动计算
  • 社区规模小于Redux,第三方扩展较少

Recoil:原子化状态管理

Recoil是Facebook推出的状态管理库,设计理念基于"原子状态"和"选择器",提供了细粒度的状态控制和状态派生能力。

原子(Atoms)

原子是Recoil中最小的状态单位,类似于React的useState,但可以在组件间共享:

jsx 复制代码
import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

// 定义原子状态
const countAtom = atom({
  key: 'countAtom', // 全局唯一的标识符
  default: 0,       // 默认值
});

function Counter() {
  // 类似useState的用法
  const [count, setCount] = useRecoilState(countAtom);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

function DisplayCount() {
  // 只读状态,不会触发不必要的重渲染
  const count = useRecoilValue(countAtom);
  
  return <div>Current count: {count}</div>;
}

function CounterControls() {
  // 只获取setter函数,不订阅状态变化
  const setCount = useSetRecoilState(countAtom);
  
  return (
    <div>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(c => c + 5)}>+5</button>
    </div>
  );
}

原子的关键特性是可以精确控制哪些组件订阅哪些状态,从而优化渲染性能。每个原子变化只会触发使用该原子的组件重新渲染。

选择器(Selectors)

选择器是从原子或其他选择器派生的计算状态,类似于Redux的选择器或Vue的计算属性:

jsx 复制代码
import { atom, selector, useRecoilValue } from 'recoil';

// 基础原子
const countAtom = atom({
  key: 'countAtom',
  default: 0,
});

// 派生状态
const doubleCountSelector = selector({
  key: 'doubleCount',
  get: ({get}) => {
    const count = get(countAtom); // 获取依赖的原子状态
    return count * 2;
  },
});

// 多atom依赖的复杂选择器
const todoListAtom = atom({
  key: 'todoList',
  default: [],
});

const todoFilterAtom = atom({
  key: 'todoFilter',
  default: 'all', // 'all' | 'completed' | 'uncompleted'
});

const filteredTodoListSelector = selector({
  key: 'filteredTodoList',
  get: ({get}) => {
    const filter = get(todoFilterAtom);
    const list = get(todoListAtom);
    
    switch (filter) {
      case 'completed':
        return list.filter(item => item.completed);
      case 'uncompleted':
        return list.filter(item => !item.completed);
      default:
        return list;
    }
  },
});

function TodoStats() {
  const count = useRecoilValue(countAtom);
  const doubleCount = useRecoilValue(doubleCountSelector);
  const filteredTodos = useRecoilValue(filteredTodoListSelector);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <p>Filtered todos: {filteredTodos.length}</p>
    </div>
  );
}

选择器自动追踪依赖关系,当依赖的原子状态变化时,选择器会重新计算并通知使用该选择器的组件更新。

异步选择器

Recoil支持异步选择器,可以直接处理API请求等异步操作:

jsx 复制代码
const userInfoQuery = selectorFamily({
  key: 'userInfo',
  get: (userId) => async () => {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    if (!response.ok) throw new Error('Failed to fetch user');
    return await response.json();
  },
});

function UserProfile({ userId }) {
  const userInfo = useRecoilValue(userInfoQuery(userId));
  
  return (
    <div>
      <h1>{userInfo.name}</h1>
      <p>Email: {userInfo.email}</p>
    </div>
  );
}

使用Suspense和ErrorBoundary可以优雅处理加载和错误状态:

jsx 复制代码
function App() {
  return (
    <RecoilRoot>
      <ErrorBoundary fallback={<div>Error loading user</div>}>
        <React.Suspense fallback={<div>Loading...</div>}>
          <UserProfile userId="123" />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

适用场景:需要细粒度状态控制与派生状态的复杂应用,特别是有大量相互依赖状态的场景。典型用例包括数据分析应用、复杂表单、实时协作工具等。

优势

  • 原子化状态设计,提供最细粒度的控制
  • 强大的派生状态(selector)能力,自动追踪依赖
  • 异步状态处理简洁,与React Suspense集成良好
  • Facebook官方支持,与React理念一致
  • 出色的状态依赖追踪,优化渲染性能
  • 基于钩子的简洁API,学习曲线平缓
  • 天然支持代码分割,适合大型应用

局限性

  • API相对复杂,概念较多
  • 需要RecoilRoot包裹,额外的设置步骤
  • 相比其他方案生态系统不够成熟
  • 文档和最佳实践相对缺乏
  • 持久化等高级功能需要额外实现
  • 尚未达到1.0版本,API可能变动

Jotai:轻量级原子状态

Jotai是受Recoil启发的轻量级原子状态管理库,提供了更简化的API和更小的体积。

基本使用

Jotai的基本API比Recoil更简洁:

jsx 复制代码
import { atom, useAtom } from 'jotai';

// 创建原子
const countAtom = atom(0);

// 派生原子
const doubleAtom = atom(get => get(countAtom) * 2);

// 可写的派生原子
const incrementAtom = atom(
  get => get(countAtom),
  (get, set, arg) => set(countAtom, get(countAtom) + 1)
);

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleAtom);
  const [, increment] = useAtom(incrementAtom); // 只使用写入器
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Jotai的原子可以依赖其他原子,创建复杂的状态依赖图,同时保持API简洁。

异步原子

Jotai支持异步原子,可以直接处理Promise:

jsx 复制代码
const userAtom = atom(async () => {
  const response = await fetch('https://api.example.com/user');
  return response.json();
});

// 或者使用已有原子创建异步派生
const userIdAtom = atom('123');
const userByIdAtom = atom(async get => {
  const id = get(userIdAtom);
  const response = await fetch(`https://api.example.com/users/${id}`);
  return response.json();
});

function UserProfile() {
  const [user] = useAtom(userByIdAtom);
  
  return (
    <div>
      {user.loading ? (
        <p>Loading...</p>
      ) : (
        <>
          <h1>{user.name}</h1>
          <p>Email: {user.email}</p>
        </>
      )}
    </div>
  );
}

与Recoil类似,Jotai也可以与React Suspense结合使用。

适用场景:需要Recoil类似功能但希望更轻量的应用,对状态颗粒度有高要求的项目。典型用例包括交互性强的UI组件、需要细粒度状态控制的小型应用等。

优势

  • 极简API,核心概念少
  • 体积小(约2.5KB),性能优秀
  • 无需Provider包裹(可选)
  • 良好的TypeScript支持
  • 原子化设计,优化渲染性能
  • 与React Suspense兼容
  • 支持与Immer集成,简化不可变更新

局限性

  • 相对较新,生态不够成熟
  • 大型应用状态组织能力有限
  • 文档和示例相对简单
  • 缺乏专门的调试工具
  • 社区支持不如Redux和Recoil

实际项目选型决策框架

项目规模与复杂度评估

选择状态管理方案首先需要考虑项目规模和复杂度,不同规模的项目有不同的最佳实践:

小型应用(组件数<20)

  • 推荐:React内置状态管理(useState + useContext)

    • 理由:无需额外依赖,学习成本低,对于简单应用足够用
    • 实施方案:创建几个针对性的Context,避免全局Context过大导致性能问题
  • 替代:Zustand(需要更好的开发体验)

    • 理由:API简单,几乎零配置,可以逐步采用
    • 实施方案:创建单一store,根据功能模块组织状态
jsx 复制代码
// 小型应用使用Context的最佳实践
// 按照功能拆分多个Context,而不是使用单一全局Context
const AuthContext = React.createContext(null);
const ThemeContext = React.createContext(null);
const FeatureFlagsContext = React.createContext(null);

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [features, setFeatures] = useState({
    newDashboard: false,
    betaFeatures: false
  });
  
  // 登录逻辑
  const login = async (credentials) => {
    // 实现登录逻辑
    const user = await loginApi(credentials);
    setUser(user);
  };
  
  const logout = () => setUser(null);
  
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        <FeatureFlagsContext.Provider value={{ features, setFeatures }}>
          <MainApp />
        </FeatureFlagsContext.Provider>
      </ThemeContext.Provider>
    </AuthContext.Provider>
  );
}

中型应用(组件数20-100)

  • 推荐:Zustand或Jotai

    • 理由:平衡了简洁性和功能性,学习成本适中
    • 实施方案:按功能域划分状态切片,避免单一巨大store
  • 替代:Redux Toolkit(预计未来会扩展)

    • 理由:为未来应用增长提供扩展性,规范化的状态管理
    • 实施方案:使用RTK,按照领域模型组织状态,避免过度设计
jsx 复制代码
// 中型应用使用Zustand的状态组织最佳实践
// 1. 按功能划分状态切片
const createAuthSlice = (set, get) => ({
  user: null,
  loading: false,
  error: null,
  login: async (credentials) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) throw new Error('Login failed');
      const user = await response.json();
      set({ user, loading: false });
      return user;
    } catch (error) {
      set({ error: error.message, loading: false });
      throw error;
    }
  },
  logout: () => set({ user: null })
});

const createProductsSlice = (set, get) => ({
  products: [],
  loading: false,
  error: null,
  fetchProducts: async (category) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`/api/products?category=${category}`);
      if (!response.ok) throw new Error('Failed to fetch products');
      const products = await response.json();
      set({ products, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
});

// 2. 组合所有切片
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(
  persist(
    (set, get) => ({
      ...createAuthSlice(set, get),
      ...createProductsSlice(set, get)
    }),
    { name: 'app-store' } // 持久化到localStorage
  )
);

// 3. 在组件中使用,只订阅需要的部分状态
function ProductList({ category }) {
  // 只订阅products相关状态,auth状态变化不会导致此组件重渲染
  const { products, loading, error, fetchProducts } = useStore(
    state => ({
      products: state.products,
      loading: state.loading,
      error: state.error,
      fetchProducts: state.fetchProducts
    })
  );
  
  useEffect(() => {
    fetchProducts(category);
  }, [category, fetchProducts]);
  
  if (loading) return <div>Loading products...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div className="product-list">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

大型应用(组件数>100)

  • 推荐:Redux Toolkit

    • 理由:成熟的生态系统,严格的状态更新规则,利于团队协作
    • 实施方案:按领域模型设计状态结构,规范化状态管理流程
  • 替代:Recoil(特别是对原子化状态有需求时)

    • 理由:细粒度状态控制,适合复杂UI和频繁局部更新
    • 实施方案:按功能设计原子族,利用选择器优化派生状态
jsx 复制代码
// 大型应用使用Redux Toolkit的组织方式
// 1. 按领域模型划分状态切片
// auth/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const loginUser = createAsyncThunk(
  'auth/login',
  async (credentials, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) throw new Error('Login failed');
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null
  },
  reducers: {
    logout: (state) => {
      state.user = null;
      state.status = 'idle';
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(loginUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.user = action.payload;
        state.error = null;
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload;
      });
  }
});

export const { logout } = authSlice.actions;
export default authSlice.reducer;

// products/productsSlice.js, orders/ordersSlice.js 等按类似方式实现

// 2. 组合所有slice到store
// store.js
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './auth/authSlice';
import productsReducer from './products/productsSlice';
import ordersReducer from './orders/ordersSlice';

export const store = configureStore({
  reducer: {
    auth: authReducer,
    products: productsReducer,
    orders: ordersReducer
  }
});

// 3. 提供选择器函数优化组件重渲染
// auth/selectors.js
export const selectUser = state => state.auth.user;
export const selectAuthStatus = state => state.auth.status;
export const selectAuthError = state => state.auth.error;

// 4. 组件中使用
import { useSelector, useDispatch } from 'react-redux';
import { loginUser } from './auth/authSlice';
import { selectUser, selectAuthStatus, selectAuthError } from './auth/selectors';

function LoginPage() {
  const dispatch = useDispatch();
  const user = useSelector(selectUser);
  const status = useSelector(selectAuthStatus);
  const error = useSelector(selectAuthError);
  
  const handleSubmit = async (event) => {
    event.preventDefault();
    const credentials = {
      email: event.target.email.value,
      password: event.target.password.value
    };
    await dispatch(loginUser(credentials));
  };
  
  // 渲染逻辑...
}

团队因素考量

技术选型不仅取决于技术本身,还与团队组成和技术背景密切相关:

React新手团队

  • 推荐 :内置状态管理 → Zustand
    • 理由:学习曲线平缓,API直观,减少认知负担
    • 过渡策略:先掌握React基础,再逐步引入状态管理
    • 避免:直接上手复杂的Redux或Recoil

有Redux经验团队

  • 推荐 :继续使用Redux Toolkit
    • 理由:团队已有经验,迁移成本低,RTK提供更现代的开发体验
    • 现代化策略:从传统Redux迁移到RTK,利用新API简化代码
    • 考虑:在新模块尝试Zustand等轻量方案,对比开发体验

技术前沿团队

  • 推荐 :尝试Jotai或Recoil等新兴方案
    • 理由:探索更现代的状态管理范式,提升开发体验
    • 策略:核心模块使用成熟方案,新功能尝试新技术
    • 平衡:创新与稳定性,避免过度追求新技术

开发体验与工具链集成

状态管理的选择还应考虑与现有工具链的集成体验:

TypeScript支持

  • Redux Toolkit、Zustand和Jotai对TypeScript支持良好
  • Recoil的类型推导在复杂场景可能需要额外类型标注

开发者工具

  • Redux DevTools支持最完善,提供时间旅行调试、action记录等
  • Zustand和Jotai可以集成Redux DevTools,但功能相对有限
  • Recoil有专门的开发者工具,但功能不如Redux DevTools成熟

热重载支持

  • 所有方案都支持React的热重载
  • Redux需要额外配置保持热重载时的状态
  • Zustand和Jotai通常无需额外配置

案例分析:电商平台状态管理

以典型电商平台为例,不同模块可能需要不同的状态管理方案,体现了实际项目中的混合策略:

用户认证模块

  • 选择:Redux Toolkit
  • 原因
    • 需要在整个应用中共享用户状态
    • 有复杂的中间件处理(JWT刷新、权限检查)
    • 状态变化需要追踪审计
    • 与路由守卫等功能集成
jsx 复制代码
// Redux实现用户认证
const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: null,
    token: localStorage.getItem('token'),
    refreshToken: localStorage.getItem('refreshToken'),
    isLoading: false,
    error: null,
    permissions: []
  },
  reducers: {
    loginStart: state => {
      state.isLoading = true;
    },
    loginSuccess: (state, action) => {
      state.isLoading = false;
      state.user = action.payload.user;
      state.token = action.payload.token;
      state.refreshToken = action.payload.refreshToken;
      state.permissions = action.payload.permissions || [];
      state.error = null;
      
      // 保存到localStorage
      localStorage.setItem('token', action.payload.token);
      localStorage.setItem('refreshToken', action.payload.refreshToken);
    },
    loginFailure: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
    logout: state => {
      state.user = null;
      state.token = null;
      state.refreshToken = null;
      state.permissions = [];
      
      // 清除localStorage
      localStorage.removeItem('token');
      localStorage.removeItem('refreshToken');
    },
    // 更新token(用于刷新token流程)
    updateToken: (state, action) => {
      state.token = action.payload;
      localStorage.setItem('token', action.payload);
    }
  }
});

// Token刷新中间件
const refreshTokenMiddleware = store => next => async action => {
  const result = next(action);
  
  // 当接收到401错误时,尝试刷新token
  if (
    action.type === 'api/requestFailed' && 
    action.payload?.status === 401 &&
    store.getState().auth.refreshToken
  ) {
    try {
      const response = await fetch('/api/refresh-token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          refreshToken: store.getState().auth.refreshToken 
        })
      });
      
      if (response.ok) {
        const data = await response.json();
        // 更新token
        store.dispatch(updateToken(data.token));
        // 重试失败的请求
        store.dispatch(action.meta.originalRequest);
      } else {
        // 刷新失败,登出用户
        store.dispatch(logout());
      }
    } catch (error) {
      store.dispatch(logout());
    }
  }
  
  return result;
};

// 在configureStore中添加中间件
const store = configureStore({
  reducer: {
    auth: authSlice.reducer,
    // 其他reducer...
  },
  middleware: (getDefaultMiddleware) => 
    getDefaultMiddleware().concat(refreshTokenMiddleware)
});

购物车模块

  • 选择:Zustand + 持久化
  • 原因
    • 需要频繁更新且有持久化需求
    • 逻辑相对独立,与其他状态耦合较少
    • 需要高性能更新,避免整个应用重渲染
    • 用户体验要求购物车在刷新页面后保持状态
jsx 复制代码
// Zustand实现购物车
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useCartStore = create(
  persist(
    (set, get) => ({
      items: [],
      totalItems: 0,
      totalPrice: 0,
      
      // 添加商品到购物车
      addItem: (product) => set(state => {
        const existingItem = state.items.find(item => item.id === product.id);
        
        if (existingItem) {
          const updatedItems = state.items.map(item => 
            item.id === product.id 
              ? {...item, quantity: item.quantity + 1} 
              : item
          );
          
          return {
            items: updatedItems,
            totalItems: state.totalItems + 1,
            totalPrice: state.totalPrice + product.price
          };
        }
        
        return {
          items: [...state.items, {...product, quantity: 1}],
          totalItems: state.totalItems + 1,
          totalPrice: state.totalPrice + product.price
        };
      }),
      
      // 从购物车移除商品
      removeItem: (productId) => set(state => {
        const itemToRemove = state.items.find(item => item.id === productId);
        if (!itemToRemove) return state;
        
        return {
          items: state.items.filter(item => item.id !== productId),
          totalItems: state.totalItems - itemToRemove.quantity,
          totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity)
        };
      }),
      
      // 更新商品数量
      updateQuantity: (productId, quantity) => set(state => {
        const item = state.items.find(item => item.id === productId);
        if (!item) return state;
        
        const quantityDiff = quantity - item.quantity;
        const priceDiff = item.price * quantityDiff;
        
        return {
          items: state.items.map(item =>
            item.id === productId
              ? { ...item, quantity }
              : item
          ),
          totalItems: state.totalItems + quantityDiff,
          totalPrice: state.totalPrice + priceDiff
        };
      }),
      
      // 清空购物车
      clearCart: () => set({
        items: [],
        totalItems: 0,
        totalPrice: 0
      }),
      
      // 应用优惠券
      applyDiscount: (discountPercent) => set(state => ({
        totalPrice: state.totalPrice * (1 - discountPercent / 100)
      }))
    }),
    {
      name: 'cart-storage', // localStorage的键名
      getStorage: () => localStorage, // 使用localStorage
      partialize: state => ({ 
        // 只持久化这些字段,忽略计算字段
        items: state.items,
        totalItems: state.totalItems,
        totalPrice: state.totalPrice
      })
    }
  )
);

// 使用购物车状态
function CartSummary() {
  const { totalItems, totalPrice } = useCartStore();
  
  return (
    <div className="cart-summary">
      <span>购物车: {totalItems} 件商品</span>
      <span>总价: ¥{totalPrice.toFixed(2)}</span>
    </div>
  );
}

function CartDetails() {
  const { items, removeItem, updateQuantity, clearCart } = useCartStore();
  
  return (
    <div className="cart-details">
      <h2>购物车</h2>
      
      {items.length === 0 ? (
        <p>购物车为空</p>
      ) : (
        <>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                <img src={item.image} alt={item.name} />
                <div>
                  <h3>{item.name}</h3>
                  <p>¥{item.price.toFixed(2)} × {item.quantity}</p>
                  <div className="quantity-controls">
                    <button onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}>
                      -
                    </button>
                    <span>{item.quantity}</span>
                    <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>
                      +
                    </button>
                  </div>
                </div>
                <button onClick={() => removeItem(item.id)}>删除</button>
              </li>
            ))}
          </ul>
          
          <div className="cart-actions">
            <button onClick={clearCart}>清空购物车</button>
            <button>结算</button>
          </div>
        </>
      )}
    </div>
  );
}

产品详情页

  • 选择:React Query + 局部useState
  • 原因
    • 主要是服务器状态(产品数据)
    • 局部UI状态相对简单(选中的变体、数量等)
    • 需要缓存和预取数据,减少重复请求
    • 自动处理加载和错误状态

中型应用(组件数20-100)

  • 推荐:Zustand或Jotai

    • 理由:平衡了简洁性和功能性,学习成本适中
    • 实施方案:按功能域划分状态切片,避免单一巨大store
  • 替代:Redux Toolkit(预计未来会扩展)

    • 理由:为未来应用增长提供扩展性,规范化的状态管理
    • 实施方案:使用RTK,按照领域模型组织状态,避免过度设计
jsx 复制代码
// 1. 按功能划分状态切片
const createAuthSlice = (set, get) => ({
  user: null,
  loading: false,
  error: null,
  login: async (credentials) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) throw new Error('Login failed');
      const user = await response.json();
      set({ user, loading: false });
      return user;
    } catch (error) {
      set({ error: error.message, loading: false });
      throw error;
    }
  },
  logout: () => set({ user: null })
});

const createProductsSlice = (set, get) => ({
  products: [],
  loading: false,
  error: null,
  fetchProducts: async (category) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`/api/products?category=${category}`);
      if (!response.ok) throw new Error('Failed to fetch products');
      const products = await response.json();
      set({ products, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
});

// 2. 组合所有切片
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(
  persist(
    (set, get) => ({
      ...createAuthSlice(set, get),
      ...createProductsSlice(set, get)
    }),
    { name: 'app-store' } // 持久化到localStorage
  )
);

大型应用(组件数>100)

  • 推荐:Redux Toolkit

    • 理由:成熟的生态系统,严格的状态更新规则,利于团队协作
    • 实施方案:按领域模型设计状态结构,规范化状态管理流程
  • 替代:Recoil(特别是对原子化状态有需求时)

    • 理由:细粒度状态控制,适合复杂UI和频繁局部更新
    • 实施方案:按功能设计原子族,利用选择器优化派生状态
jsx 复制代码
// 大型应用使用Redux Toolkit的组织方式
// auth/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const loginUser = createAsyncThunk(
  'auth/login',
  async (credentials, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' }
      });
      if (!response.ok) throw new Error('Login failed');
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null
  },
  reducers: {
    logout: (state) => {
      state.user = null;
      state.status = 'idle';
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(loginUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.user = action.payload;
        state.error = null;
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload;
      });
  }
});

团队因素考量

技术选型不仅取决于技术本身,还与团队组成和技术背景密切相关:

React新手团队

  • 推荐 :内置状态管理 → Zustand
    • 理由:学习曲线平缓,API直观,减少认知负担
    • 过渡策略:先掌握React基础,再逐步引入状态管理
    • 避免:直接上手复杂的Redux或Recoil

有Redux经验团队

  • 推荐 :继续使用Redux Toolkit
    • 理由:团队已有经验,迁移成本低,RTK提供更现代的开发体验
    • 现代化策略:从传统Redux迁移到RTK,利用新API简化代码
    • 考虑:在新模块尝试Zustand等轻量方案,对比开发体验

技术前沿团队

  • 推荐 :尝试Jotai或Recoil等新兴方案
    • 理由:探索更现代的状态管理范式,提升开发体验
    • 策略:核心模块使用成熟方案,新功能尝试新技术
    • 平衡:创新与稳定性,避免过度追求新技术

案例分析:电商平台状态管理

以典型电商平台为例,不同模块可能需要不同的状态管理方案,体现了实际项目中的混合策略:

用户认证模块

  • 选择:Redux Toolkit
  • 原因
    • 需要在整个应用中共享用户状态
    • 有复杂的中间件处理(JWT刷新、权限检查)
    • 状态变化需要追踪审计
    • 与路由守卫等功能集成
jsx 复制代码
// Redux实现用户认证
const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: null,
    token: localStorage.getItem('token'),
    refreshToken: localStorage.getItem('refreshToken'),
    isLoading: false,
    error: null,
    permissions: []
  },
  reducers: {
    loginStart: state => {
      state.isLoading = true;
    },
    loginSuccess: (state, action) => {
      state.isLoading = false;
      state.user = action.payload.user;
      state.token = action.payload.token;
      state.refreshToken = action.payload.refreshToken;
      state.permissions = action.payload.permissions || [];
      state.error = null;
      
      // 保存到localStorage
      localStorage.setItem('token', action.payload.token);
      localStorage.setItem('refreshToken', action.payload.refreshToken);
    },
    loginFailure: (state, action) => {
      state.isLoading = false;
      state.error = action.payload;
    },
    logout: state => {
      state.user = null;
      state.token = null;
      state.refreshToken = null;
      state.permissions = [];
      
      // 清除localStorage
      localStorage.removeItem('token');
      localStorage.removeItem('refreshToken');
    }
  }
});

购物车模块

  • 选择:Zustand + 持久化
  • 原因
    • 需要频繁更新且有持久化需求
    • 逻辑相对独立,与其他状态耦合较少
    • 需要高性能更新,避免整个应用重渲染
    • 用户体验要求购物车在刷新页面后保持状态
jsx 复制代码
// Zustand实现购物车
import create from 'zustand';
import { persist } from 'zustand/middleware';

const useCartStore = create(
  persist(
    (set, get) => ({
      items: [],
      totalItems: 0,
      totalPrice: 0,
      
      // 添加商品到购物车
      addItem: (product) => set(state => {
        const existingItem = state.items.find(item => item.id === product.id);
        
        if (existingItem) {
          const updatedItems = state.items.map(item => 
            item.id === product.id 
              ? {...item, quantity: item.quantity + 1} 
              : item
          );
          
          return {
            items: updatedItems,
            totalItems: state.totalItems + 1,
            totalPrice: state.totalPrice + product.price
          };
        }
        
        return {
          items: [...state.items, {...product, quantity: 1}],
          totalItems: state.totalItems + 1,
          totalPrice: state.totalPrice + product.price
        };
      }),
      
      // 从购物车移除商品
      removeItem: (productId) => set(state => {
        const itemToRemove = state.items.find(item => item.id === productId);
        if (!itemToRemove) return state;
        
        return {
          items: state.items.filter(item => item.id !== productId),
          totalItems: state.totalItems - itemToRemove.quantity,
          totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity)
        };
      })
    }),
    {
      name: 'cart-storage', // localStorage的键名
      getStorage: () => localStorage // 使用localStorage
    }
  )
);

产品详情页

  • 选择:React Query + 局部useState
  • 原因
    • 主要是服务器状态(产品数据)
    • 局部UI状态相对简单(选中的变体、数量等)
    • 需要缓存和预取数据,减少重复请求
    • 自动处理加载和错误状态
jsx 复制代码
import { useQuery } from 'react-query';
import { useState } from 'react';

function ProductDetail({ productId }) {
  // 服务器状态管理 - React Query
  const { data: product, isLoading, error } = useQuery(
    ['product', productId],
    () => fetchProductById(productId),
    {
      staleTime: 5 * 60 * 1000, // 5分钟内数据不过期
      cacheTime: 60 * 60 * 1000 // 缓存1小时
    }
  );
  
  // 局部UI状态 - React useState
  const [selectedVariant, setSelectedVariant] = useState(null);
  const [quantity, setQuantity] = useState(1);
  
  // 购物车状态(来自Zustand)
  const addItem = useCartStore(state => state.addItem);
  
  useEffect(() => {
    // 产品加载完成后,默认选择第一个变体
    if (product?.variants?.length > 0) {
      setSelectedVariant(product.variants[0]);
    }
  }, [product]);
  
  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!product) return <NotFoundMessage />;
  
  const handleAddToCart = () => {
    if (!selectedVariant) return;
    
    addItem({
      id: `${product.id}-${selectedVariant.id}`,
      productId: product.id,
      variantId: selectedVariant.id,
      name: product.name,
      variantName: selectedVariant.name,
      price: selectedVariant.price,
      image: product.images[0]
    });
    
    // 显示添加成功提示
    toast.success('已添加到购物车');
  };
  
  return (
    <div className="product-detail">
      <div className="product-images">
        <ProductImageGallery images={product.images} />
      </div>
      
      <div className="product-info">
        <h1>{product.name}</h1>
        <div className="product-price">
          {selectedVariant ? (
            <span>¥{selectedVariant.price.toFixed(2)}</span>
          ) : (
            <span>¥{product.price.toFixed(2)}</span>
          )}
        </div>
        
        {product.variants.length > 0 && (
          <div className="variant-selector">
            <h3>选择规格</h3>
            <div className="variant-options">
              {product.variants.map(variant => (
                <button
                  key={variant.id}
                  className={selectedVariant?.id === variant.id ? 'selected' : ''}
                  onClick={() => setSelectedVariant(variant)}
                >
                  {variant.name}
                </button>
              ))}
            </div>
          </div>
        )}
        
        <div className="quantity-selector">
          <h3>数量</h3>
          <div className="quantity-control">
            <button 
              onClick={() => setQuantity(Math.max(1, quantity - 1))}
              disabled={quantity <= 1}
            >
              -
            </button>
            <span>{quantity}</span>
            <button onClick={() => setQuantity(quantity + 1)}>
              +
            </button>
          </div>
        </div>
        
        <button 
          className="add-to-cart-button"
          onClick={handleAddToCart}
          disabled={!selectedVariant}
        >
          加入购物车
        </button>
      </div>
      
      <div className="product-description">
        <h2>商品详情</h2>
        <div dangerouslySetInnerHTML={{ __html: product.description }} />
      </div>
    </div>
  );
}

商品列表和筛选

  • 选择:Jotai
  • 原因
    • 需要细粒度的状态更新(价格范围、分类筛选等)
    • 页面组件较多,需要避免不必要的重渲染
    • 筛选条件需要在多个组件间共享
    • URL参数与状态需要同步
jsx 复制代码
import { atom, useAtom } from 'jotai';
import { useUpdateEffect } from 'react-use';
import { useNavigate, useLocation } from 'react-router-dom';

// 定义原子状态
const categoryAtom = atom('all');
const priceRangeAtom = atom({ min: 0, max: 10000 });
const sortByAtom = atom('popularity'); // 'popularity', 'price-low', 'price-high', 'newest'
const pageAtom = atom(1);
const pageSizeAtom = atom(20);

// 派生状态 - URL查询参数
const queryParamsAtom = atom(
  get => {
    const category = get(categoryAtom);
    const { min, max } = get(priceRangeAtom);
    const sortBy = get(sortByAtom);
    const page = get(pageAtom);
    const pageSize = get(pageSizeAtom);
    
    const params = new URLSearchParams();
    if (category !== 'all') params.set('category', category);
    if (min > 0) params.set('min_price', min.toString());
    if (max < 10000) params.set('max_price', max.toString());
    if (sortBy !== 'popularity') params.set('sort', sortBy);
    if (page > 1) params.set('page', page.toString());
    if (pageSize !== 20) params.set('page_size', pageSize.toString());
    
    return params.toString();
  }
);

function ProductListPage() {
  // Jotai状态
  const [category, setCategory] = useAtom(categoryAtom);
  const [priceRange, setPriceRange] = useAtom(priceRangeAtom);
  const [sortBy, setSortBy] = useAtom(sortByAtom);
  const [page, setPage] = useAtom(pageAtom);
  const [queryParams] = useAtom(queryParamsAtom);
  
  // 路由和URL参数
  const navigate = useNavigate();
  const location = useLocation();
  
  // 从URL同步状态
  useEffect(() => {
    const params = new URLSearchParams(location.search);
    
    const categoryParam = params.get('category');
    if (categoryParam) setCategory(categoryParam);
    
    const minPrice = params.get('min_price');
    const maxPrice = params.get('max_price');
    if (minPrice || maxPrice) {
      setPriceRange({
        min: minPrice ? parseInt(minPrice) : 0,
        max: maxPrice ? parseInt(maxPrice) : 10000
      });
    }
    
    const sortParam = params.get('sort');
    if (sortParam) setSortBy(sortParam);
    
    const pageParam = params.get('page');
    if (pageParam) setPage(parseInt(pageParam));
  }, []);
  
  // 同步状态到URL
  useUpdateEffect(() => {
    navigate({ search: queryParams }, { replace: true });
  }, [queryParams, navigate]);
  
  // 使用React Query获取产品数据
  const { data, isLoading, error } = useQuery(
    ['products', queryParams],
    () => fetchProducts(queryParams),
    { keepPreviousData: true }
  );
  
  return (
    <div className="product-list-page">
      <div className="filters-sidebar">
        <CategoryFilter
          selectedCategory={category}
          onChange={setCategory}
        />
        
        <PriceRangeFilter
          value={priceRange}
          onChange={setPriceRange}
        />
        
        <SortOptions
          value={sortBy}
          onChange={setSortBy}
        />
      </div>
      
      <div className="product-grid">
        {isLoading ? (
          <LoadingSpinner />
        ) : error ? (
          <ErrorMessage error={error} />
        ) : (
          <>
            {data.products.map(product => (
              <ProductCard key={product.id} product={product} />
            ))}
            
            <Pagination
              currentPage={page}
              totalPages={data.totalPages}
              onPageChange={setPage}
            />
          </>
        )}
      </div>
    </div>
  );
}

// 过滤器组件
function CategoryFilter({ selectedCategory, onChange }) {
  const { data: categories } = useQuery(
    'categories',
    fetchCategories
  );
  
  if (!categories) return null;
  
  return (
    <div className="filter-section">
      <h3>商品分类</h3>
      <ul>
        <li>
          <label>
            <input
              type="radio"
              checked={selectedCategory === 'all'}
              onChange={() => onChange('all')}
            />
            全部商品
          </label>
        </li>
        {categories.map(category => (
          <li key={category.id}>
            <label>
              <input
                type="radio"
                checked={selectedCategory === category.id}
                onChange={() => onChange(category.id)}
              />
              {category.name}
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
}

总结与决策

选择合适的状态管理方案没有放之四海而皆准的答案,需要根据项目规模、团队经验、性能需求和未来可维护性综合考量。基于前文的分析,我们可以提炼出以下核心决策原则:

  1. 渐进式采用:从简单方案开始,随需求复杂度增加逐步引入强大工具。对于新项目,优先考虑内置状态管理,当确实需要全局状态时再引入专门的库。

  2. 混合策略:针对不同类型状态选择最适合的管理方式:

    • 服务器状态:优先考虑React Query/SWR
    • 全局UI状态:Redux/Zustand/Recoil
    • 局部UI状态:useState/useReducer
    • 表单状态:专用表单库
  3. 团队一致性:避免在一个项目中使用过多不同的状态管理库,造成代码风格不一致和维护困难。选择符合团队技术栈和经验水平的方案。

  4. 关注开发体验:选择有良好开发者工具支持的方案,能显著提高调试效率和代码质量。Redux DevTools和React DevTools是不可或缺的工具。

  5. 权衡取舍:在代码复杂度、性能、学习成本之间找到平衡点,不要盲目追求最新技术。每个方案都有其优缺点,没有完美解决方案。

  6. 考虑长期维护:评估库的社区活跃度、更新频率和长期支持情况,避免选择可能被废弃的技术。

  7. 关注性能影响:状态管理方案会直接影响应用性能,尤其是组件重渲染,应进行充分测试评估。

最终,我们会发现,最好的状态管理方案是能够让团队高效工作、保持代码可维护性、提供良好用户体验的方案。技术选型应该服务于业务目标,而非成为目标本身。

参考资源

官方文档

深入学习资料

工具与扩展

社区资源


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关推荐
二十雨辰1 分钟前
[HTML5]快速掌握canvas
前端·html
tingkeiii32 分钟前
【react+antd+vite】优雅的引入svg和阿里巴巴图标
前端·react.js·前端框架
清幽竹客38 分钟前
vue-18(使用 Vuex 插件实现高级功能)
前端·vue.js·前端框架·vue
粥里有勺糖1 小时前
用Trae做了个公众号小工具
前端·ai编程·trae
京东零售技术2 小时前
在京东 探索技术的无限可能
面试
棉花糖超人2 小时前
【从0-1的HTML】第2篇:HTML标签
前端·html
exploration-earth2 小时前
本地优先的状态管理与工具选型策略
开发语言·前端·javascript
OpenTiny社区2 小时前
开源之夏报名倒计时3天!还有9个前端任务有余位,快来申请吧~
前端·github
ak啊2 小时前
WebGL魔法:从立方体到逼真阴影的奇妙之旅
前端·webgl
hang_bro3 小时前
使用js方法实现阻止按钮的默认点击事件&触发默认事件
前端·react.js·html