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

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

参考资源

官方文档

深入学习资料

工具与扩展

社区资源


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

终身学习,共同成长。

咱们下一期见

💻

相关推荐
gnip43 分钟前
链式调用和延迟执行
前端·javascript
SoaringHeart1 小时前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.1 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu1 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss1 小时前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师1 小时前
React面试题
前端·javascript·react.js
木兮xg1 小时前
react基础篇
前端·react.js·前端框架
ssshooter2 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT利刃出鞘2 小时前
HTML--最简的二级菜单页面
前端·html
yume_sibai2 小时前
HTML HTML基础(4)
前端·html