React Redux 与 Zustand

打印 上一主题 下一主题

主题 1611|帖子 1611|积分 4833

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Redux

一、Redux 焦点概念

1. 为什么需要 Redux?



  • 办理的问题:在大型 React 应用中,跨组件共享状态、管理复杂数据流。
  • 优势

    • 单一数据源:全局状态集中存储在 Store 中。
    • 可预测性:通过严格的规则(纯函数、不可变性)管理状态变化。
    • 调试友好:支持时间旅行调试(Redux DevTools)。
    • 中心件支持:处理异步逻辑(如 API 调用)。


2. Redux 三大原则

原则说明单一数据源整个应用的状态存储在唯一的 Store 对象树中。状态只读只能通过 dispatch(action) 修改状态,克制直接修改。使用纯函数修改状态通过 Reducer 函数吸收旧状态和 Action,返回新状态(无副作用)。
3. 焦点概念

概念作用Store全局状态容器,通过 createStore 创建。Action描述状态变化的普通对象,必须包含 type 字段。Reducer纯函数,吸收当前 state 和 action,返回新的 state。Dispatch触发状态更新的方法,store.dispatch(action)。Middleware扩展 Redux 功能(如处理异步操作),位于 dispatch 和 Reducer 之间。
二、Redux 与 React 集成(React-Redux)

1. 安装依赖

  1. npm install redux react-redux @reduxjs/toolkit
复制代码
2. 焦点 API



  • Provider:包裹根组件,将 Store 传递给子组件。
  • useSelector:从 Store 中读取状态(替代 mapStateToProps)。
  • useDispatch:获取 dispatch 方法(替代 mapDispatchToProps)。

三、Redux 使用步骤(代码示例)

1. 定义 Reducer 和 Action

  1. // src/store/counterSlice.js(使用 Redux Toolkit)
  2. import { createSlice } from '@reduxjs/toolkit';
  3. const counterSlice = createSlice({
  4.   name: 'counter',
  5.   initialState: { value: 0 },
  6.   reducers: {
  7.     increment: (state) => {
  8.       state.value += 1; // Redux Toolkit 允许直接修改(内部使用 Immer)
  9.     },
  10.     decrement: (state) => {
  11.       state.value -= 1;
  12.     },
  13.     incrementByAmount: (state, action) => {
  14.       state.value += action.payload;
  15.     },
  16.   },
  17. });
  18. export const { increment, decrement, incrementByAmount } = counterSlice.actions;
  19. export default counterSlice.reducer;
复制代码
2. 创建 Store

  1. // src/store/index.js
  2. import { configureStore } from '@reduxjs/toolkit';
  3. import counterReducer from './counterSlice';
  4. export const store = configureStore({
  5.   reducer: {
  6.     counter: counterReducer,
  7.   },
  8. });
复制代码
3. 将 Store 注入 React 应用

  1. // src/index.js
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4. import { Provider } from 'react-redux';
  5. import App from './App';
  6. import { store } from './store';
  7. ReactDOM.render(
  8.   <Provider store={store}>
  9.     <App />
  10.   </Provider>,
  11.   document.getElementById('root')
  12. );
复制代码
4. 在组件中访问状态和触发 Action

  1. // src/components/Counter.js
  2. import React from 'react';
  3. import { useSelector, useDispatch } from 'react-redux';
  4. import { increment, decrement, incrementByAmount } from '../store/counterSlice';
  5. function Counter() {
  6.   const count = useSelector((state) => state.counter.value);
  7.   const dispatch = useDispatch();
  8.   return (
  9.     <div>
  10.       <button onClick={() => dispatch(decrement())}>-</button>
  11.       <span>{count}</span>
  12.       <button onClick={() => dispatch(increment())}>+</button>
  13.       <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
  14.     </div>
  15.   );
  16. }
  17. export default Counter;
复制代码

四、异步操作与中心件

1. 使用 Redux Thunk(处理异步逻辑)

  1. // src/store/userSlice.js
  2. import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
  3. import axios from 'axios';
  4. // 定义异步 Thunk
  5. export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
  6.   const response = await axios.get(`https://api.example.com/users/${userId}`);
  7.   return response.data;
  8. });
  9. const userSlice = createSlice({
  10.   name: 'user',
  11.   initialState: { data: null, loading: false, error: null },
  12.   extraReducers: (builder) => {
  13.     builder
  14.       .addCase(fetchUser.pending, (state) => {
  15.         state.loading = true;
  16.       })
  17.       .addCase(fetchUser.fulfilled, (state, action) => {
  18.         state.loading = false;
  19.         state.data = action.payload;
  20.       })
  21.       .addCase(fetchUser.rejected, (state, action) => {
  22.         state.loading = false;
  23.         state.error = action.error.message;
  24.       });
  25.   },
  26. });
  27. export default userSlice.reducer;
复制代码
2. 组件中调用异步 Action

  1. function UserProfile({ userId }) {
  2.   const dispatch = useDispatch();
  3.   const { data, loading, error } = useSelector((state) => state.user);
  4.   useEffect(() => {
  5.     dispatch(fetchUser(userId));
  6.   }, [dispatch, userId]);
  7.   if (loading) return <div>Loading...</div>;
  8.   if (error) return <div>Error: {error}</div>;
  9.   return <div>Username: {data.name}</div>;
  10. }
复制代码
Redux Thunk 完整使用流程:用户数据哀求


1. 创建异步 Thunk
  1. // src/store/userSlice.js
  2. import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
  3. import axios from 'axios';
  4. // 创建异步 Thunk Action
  5. export const fetchUser = createAsyncThunk(
  6.   'user/fetchUser', // 唯一标识符(推荐格式:"slice名称/action名称")
  7.   async (userId, thunkAPI) => { // 接收参数和 Thunk API
  8.     try {
  9.       const response = await axios.get(`https://api.example.com/users/${userId}`);
  10.       return response.data; // 成功时返回数据作为 payload
  11.     } catch (error) {
  12.       return thunkAPI.rejectWithValue(error.message); // 失败时传递错误信息
  13.     }
  14.   }
  15. );
复制代码
关键点:


  • createAsyncThunk 会主动生成三种 action 范例:

    • user/fetchUser/pending(哀求开始)
    • user/fetchUser/fulfilled(哀求乐成)
    • user/fetchUser/rejected(哀求失败)

  • 参数说明:

    • 第一个参数:唯一标识符(建议用 slice名称/action名称 格式)
    • 第二个参数:异步处理函数(可吸收参数和 thunkAPI 对象)


2. 创建 Slice 处理状态
  1. const userSlice = createSlice({
  2.   name: 'user', // slice 名称
  3.   initialState: { // 初始状态
  4.     data: null,   // 用户数据
  5.     loading: false, // 加载状态
  6.     error: null    // 错误信息
  7.   },
  8.   extraReducers: (builder) => {
  9.     builder
  10.       // 处理 pending 状态(请求开始)
  11.       .addCase(fetchUser.pending, (state) => {
  12.         state.loading = true;
  13.         state.error = null; // 重置错误
  14.       })
  15.       // 处理 fulfilled 状态(请求成功)
  16.       .addCase(fetchUser.fulfilled, (state, action) => {
  17.         state.loading = false;
  18.         state.data = action.payload; // 存储返回数据
  19.       })
  20.       // 处理 rejected 状态(请求失败)
  21.       .addCase(fetchUser.rejected, (state, action) => {
  22.         state.loading = false;
  23.         state.error = action.payload || action.error.message;
  24.       });
  25.   }
  26. });
  27. export default userSlice.reducer;
复制代码

3. 设置 Store
  1. // src/store/store.js
  2. import { configureStore } from '@reduxjs/toolkit';
  3. import userReducer from './userSlice';
  4. export default configureStore({
  5.   reducer: {
  6.     user: userReducer // 注册 slice
  7.   }
  8. });
复制代码

4. 在组件中使用
  1. // src/components/UserProfile.js
  2. import React, { useEffect } from 'react';
  3. import { useDispatch, useSelector } from 'react-redux';
  4. import { fetchUser } from '../store/userSlice';
  5. function UserProfile({ userId }) {
  6.   const dispatch = useDispatch();
  7.   const { data, loading, error } = useSelector(state => state.user);
  8.   useEffect(() => {
  9.     dispatch(fetchUser(userId)); // 触发异步请求
  10.   }, [dispatch, userId]);
  11.   if (loading) return <div>加载中...</div>;
  12.   if (error) return <div>错误:{error}</div>;
  13.   return (
  14.     <div>
  15.       <h2>{data.name}</h2>
  16.       <p>邮箱:{data.email}</p>
  17.     </div>
  18.   );
  19. }
  20. export default UserProfile;
复制代码

五、Redux 最佳实践

1. 项目布局



  • 推荐布局(按功能模块划分):
    1. src/
    2.   store/
    3.     slices/       // Redux Toolkit 的 Slice 文件
    4.     index.js      // Store 配置
    5.   components/     // UI 组件
    6.   features/       // 包含业务逻辑的组件
    复制代码
2. 状态设计原则



  • 最小化状态:制止冗余数据,只存储必要状态。
  • 规范化数据:使用 id 作为键,制止嵌套过深(可搭配 normalizr 库)。
3. 性能优化



  • 使用 React.memo:制止不必要的组件渲染。
  • 选择准确的状态片段:useSelector 尽量返回最小化数据。
    1. // ✅ 精确选择
    2. const count = useSelector((state) => state.counter.value);
    3. // ❌ 避免返回整个 state.counter
    4. const counter = useSelector((state) => state.counter);
    复制代码
4. 使用 Redux Toolkit



  • 优势:减少样板代码,内置 immer(允许直接修改状态)、createAsyncThunk 等工具。
  • 替代方案:手动编写 action、reducer 和中心件设置(传统 Redux)。

六、Redux 适用场景与替代方案

1. 何时使用 Redux?



  • 多个组件需要共享同一状态。
  • 状态更新逻辑复杂(如跨组件联动)。
  • 需要时间旅行调试、持久化状态或记录状态历史。
2. 轻量替代方案

方案特点Context APIReact 内置,适合简朴状态共享,但缺乏中心件、性能优化工具。RecoilFacebook 实验性状态管理库,原子化状态设计,适合复杂数据流。Zustand轻量级,基于 Hook 的状态管理,API 简洁。
七、总结



  • Redux 焦点:Store、Action、Reducer、Middleware。
  • React-Redux 集成:Provider、useSelector、useDispatch。
  • 异步处理:通过 Redux Thunk 或 createAsyncThunk 管理 API 调用。
  • 最佳实践:使用 Redux Toolkit 简化代码,公道设计状态布局。

   
Zustand

一、Zustand 焦点概念

1. 定位与特点



  • 轻量级状态管理:专为 React 设计,API 简洁,学习成本低。
  • 离开组件树:状态独立于 UI 层级,可在组件外访问。
  • 高性能:按需订阅状态片段,制止不必要的渲染。
  • 中心件支持:集成持久化、Immer(不可变更新)、日志等。

二、基础使用

1. 创建 Store

  1. // store/counterStore.ts
  2. import { create } from 'zustand';
  3. type CounterState = {
  4.   count: number;
  5.   increment: () => void;
  6.   decrement: () => void;
  7. };
  8. export const useCounterStore = create<CounterState>((set) => ({
  9.   count: 0,
  10.   increment: () => set((state) => ({ count: state.count + 1 })),
  11.   decrement: () => set((state) => ({ count: state.count - 1 })),
  12. }));
复制代码
2. 组件中使用状态

  1. import { useCounterStore } from './store/counterStore';
  2. function Counter() {
  3.   const { count, increment, decrement } = useCounterStore();
  4.   return (
  5.     <div>
  6.       <button onClick={decrement}>-</button>
  7.       <span>{count}</span>
  8.       <button onClick={increment}>+</button>
  9.     </div>
  10.   );
  11. }
复制代码

三、高级功能

1. 状态切片与选择器(Selector)

按需订阅部门状态,制止全局重新渲染:
  1. // 只订阅 count 值的变化
  2. const count = useCounterStore((state) => state.count);
复制代码
2. 异步操作

在 Store 中定义异步 Action:
  1. type UserStore = {
  2.   user: User | null;
  3.   fetchUser: (id: string) => Promise<void>;
  4. };
  5. export const useUserStore = create<UserStore>((set) => ({
  6.   user: null,
  7.   fetchUser: async (id) => {
  8.     const response = await fetch(`/api/users/${id}`);
  9.     const user = await response.json();
  10.     set({ user });
  11.   },
  12. }));
复制代码
3. 中心件(Middleware)

持久化存储(Persist)
  1. import { persist } from 'zustand/middleware';
  2. export const useAuthStore = create(
  3.   persist(
  4.     (set) => ({
  5.       token: null,
  6.       login: (token) => set({ token }),
  7.       logout: () => set({ token: null }),
  8.     }),
  9.     { name: 'auth-storage' } // 存储到 localStorage
  10.   )
  11. );
复制代码
不可变更新(Immer)
  1. import { immer } from 'zustand/middleware/immer';
  2. export const useTodoStore = create(
  3.   immer((set) => ({
  4.     todos: [],
  5.     addTodo: (text) =>
  6.       set((state) => {
  7.         state.todos.push({ text, completed: false }); // 直接修改 draft
  8.       }),
  9.   }))
  10. );
复制代码

四、最佳实践

1. 公道拆分 Store



  • 按功能模块拆分:制止单一 Store 过于痴肥。
    1. // store/userStore.ts
    2. export const useUserStore = create(...);
    3. // store/cartStore.ts
    4. export const useCartStore = create(...);
    复制代码
2. 范例安全(TypeScript)

为 Store 和 Actions 定义明确范例:
  1. type UserState = {
  2.   user: User | null;
  3.   setUser: (user: User) => void;
  4.   clearUser: () => void;
  5. };
  6. export const useUserStore = create<UserState>((set) => ({
  7.   user: null,
  8.   setUser: (user) => set({ user }),
  9.   clearUser: () => set({ user: null }),
  10. }));
复制代码
3. 性能优化



  • 使用选择器:制止订阅无关状态。
  • 结合 shallow 比力:优化对象/数组范例的状态订阅。
    1. import { shallow } from 'zustand/shallow';
    2. const { user, setUser } = useUserStore(
    3.   (state) => ({ user: state.user, setUser: state.setUser }),
    4.   shallow
    5. );
    复制代码
4. 在组件外访问 Store

直接调用 Store 方法(如 API 模块中):
  1. // 在非组件代码中
  2. import { useCounterStore } from './store/counterStore';
  3. const { increment } = useCounterStore.getState();
  4. increment();
复制代码

五、与 Redux 和 Context API 对比

特性ZustandReduxContext API复杂度极低高(需 Action、Reducer、Middleware)低性能按需订阅状态,主动优化需手动优化(如 reselect)全子树渲染,需手动优化中心件/插件支持(持久化、Immer 等)丰富(Redux Thunk、Saga 等)无适用场景中小型应用,快速开辟大型应用,需严格架构简朴状态共享,低频更新TypeScript自然支持需额外设置支持
六、常见问题与办理方案

1. Store 状态更新后组件未渲染



  • 原因:组件未订阅干系状态或选择器未正确更新。
  • 办理:检查选择器逻辑,确保状态更新触发组件重渲染。
2. 循环依赖问题



  • 场景:多个 Store 相互依赖。
  • 办理:通过 getState 在 Action 中访问其他 Store:
    1. // store/authStore.ts
    2. import { useCartStore } from './cartStore';
    3. export const useAuthStore = create((set) => ({
    4.   login: (user) => {
    5.     const { clearCart } = useCartStore.getState();
    6.     clearCart(); // 登录时清空购物车
    7.     set({ user });
    8.   },
    9. }));
    复制代码
3. Zustand 与 Next.js 服务端渲染(SSR)



  • 问题:服务端与客户端状态差别等。
  • 办理:使用 persist 中心件 + 自定义存储(如 Cookie):
    1. import { persist, createJSONStorage } from 'zustand/middleware';
    2. export const useStore = create(
    3.   persist(
    4.     (set) => ({ ... }),
    5.     {
    6.       name: 'store',
    7.       storage: createJSONStorage(() => localStorage), // 或自定义存储
    8.     }
    9.   )
    10. );
    复制代码

七、总结



  • 焦点优势:简洁、高性能、离开组件树、中心件支持。
  • 适用场景:中小型 React 应用、需快速开辟、状态共享与优化。
  • 最佳实践

    • 按功能拆分 Store。
    • 使用 TypeScript 确保范例安全。
    • 公道使用选择器和中心件优化性能。


 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表