React Redux 与 Zustand

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. 安装依赖

npm install redux react-redux @reduxjs/toolkit

2. 核心 API

  • Provider:包裹根组件,将 Store 传递给子组件。

  • useSelector:从 Store 中读取状态(替代 mapStateToProps)。

  • useDispatch:获取 dispatch 方法(替代 mapDispatchToProps)。


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

1. 定义 Reducer 和 Action

// src/store/counterSlice.js(使用 Redux Toolkit)
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1; // Redux Toolkit 允许直接修改(内部使用 Immer)
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

2. 创建 Store

// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

3. 将 Store 注入 React 应用

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

4. 在组件中访问状态和触发 Action

// src/components/Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
    </div>
  );
}

export default Counter;

四、异步操作与中间件

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

// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 定义异步 Thunk
export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
  const response = await axios.get(`https://api.example.com/users/${userId}`);
  return response.data;
});

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export default userSlice.reducer;

2. 组件中调用异步 Action

function UserProfile({ userId }) {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector((state) => state.user);

  useEffect(() => {
    dispatch(fetchUser(userId));
  }, [dispatch, userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>Username: {data.name}</div>;
}

Redux Thunk 完整使用流程:用户数据请求


1. 创建异步 Thunk

// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 创建异步 Thunk Action
export const fetchUser = createAsyncThunk(
  'user/fetchUser', // 唯一标识符(推荐格式:"slice名称/action名称")
  async (userId, thunkAPI) => { // 接收参数和 Thunk API
    try {
      const response = await axios.get(`https://api.example.com/users/${userId}`);
      return response.data; // 成功时返回数据作为 payload
    } catch (error) {
      return thunkAPI.rejectWithValue(error.message); // 失败时传递错误信息
    }
  }
);

关键点:

  • createAsyncThunk 会自动生成三种 action 类型:

    • user/fetchUser/pending(请求开始)

    • user/fetchUser/fulfilled(请求成功)

    • user/fetchUser/rejected(请求失败)

  • 参数说明:

    • 第一个参数:唯一标识符(建议用 slice名称/action名称 格式)

    • 第二个参数:异步处理函数(可接收参数和 thunkAPI 对象)


2. 创建 Slice 处理状态

const userSlice = createSlice({
  name: 'user', // slice 名称
  initialState: { // 初始状态
    data: null,   // 用户数据
    loading: false, // 加载状态
    error: null    // 错误信息
  },
  extraReducers: (builder) => {
    builder
      // 处理 pending 状态(请求开始)
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
        state.error = null; // 重置错误
      })
      // 处理 fulfilled 状态(请求成功)
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload; // 存储返回数据
      })
      // 处理 rejected 状态(请求失败)
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload || action.error.message;
      });
  }
});

export default userSlice.reducer;

3. 配置 Store

// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

export default configureStore({
  reducer: {
    user: userReducer // 注册 slice
  }
});

4. 在组件中使用

// src/components/UserProfile.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from '../store/userSlice';

function UserProfile({ userId }) {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector(state => state.user);

  useEffect(() => {
    dispatch(fetchUser(userId)); // 触发异步请求
  }, [dispatch, userId]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;

  return (
    <div>
      <h2>{data.name}</h2>
      <p>邮箱:{data.email}</p>
    </div>
  );
}

export default UserProfile;

五、Redux 最佳实践

1. 项目结构

  • 推荐结构(按功能模块划分):

    src/
      store/
        slices/       // Redux Toolkit 的 Slice 文件
        index.js      // Store 配置
      components/     // UI 组件
      features/       // 包含业务逻辑的组件

2. 状态设计原则

  • 最小化状态:避免冗余数据,只存储必要状态。

  • 规范化数据:使用 id 作为键,避免嵌套过深(可搭配 normalizr 库)。

3. 性能优化

  • 使用 React.memo:避免不必要的组件渲染。

  • 选择精确的状态片段useSelector 尽量返回最小化数据。

    // ✅ 精确选择
    const count = useSelector((state) => state.counter.value);
    
    // ❌ 避免返回整个 state.counter
    const counter = useSelector((state) => state.counter);

4. 使用 Redux Toolkit

  • 优势:减少样板代码,内置 immer(允许直接修改状态)、createAsyncThunk 等工具。

  • 替代方案:手动编写 actionreducer 和中间件配置(传统 Redux)。


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

1. 何时使用 Redux?

  • 多个组件需要共享同一状态。

  • 状态更新逻辑复杂(如跨组件联动)。

  • 需要时间旅行调试、持久化状态或记录状态历史。

2. 轻量替代方案

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

七、总结

  • Redux 核心StoreActionReducerMiddleware

  • React-Redux 集成ProvideruseSelectoruseDispatch

  • 异步处理:通过 Redux Thunk 或 createAsyncThunk 管理 API 调用。

  • 最佳实践:使用 Redux Toolkit 简化代码,合理设计状态结构。


   

Zustand

一、Zustand 核心概念

1. 定位与特点

  • 轻量级状态管理:专为 React 设计,API 简洁,学习成本低。

  • 脱离组件树:状态独立于 UI 层级,可在组件外访问。

  • 高性能:按需订阅状态片段,避免不必要的渲染。

  • 中间件支持:集成持久化、Immer(不可变更新)、日志等。


二、基础使用

1. 创建 Store

// store/counterStore.ts
import { create } from 'zustand';

type CounterState = {
  count: number;
  increment: () => void;
  decrement: () => void;
};

export const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

2. 组件中使用状态

import { useCounterStore } from './store/counterStore';

function Counter() {
  const { count, increment, decrement } = useCounterStore();
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

三、高级功能

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

按需订阅部分状态,避免全局重新渲染:

// 只订阅 count 值的变化
const count = useCounterStore((state) => state.count);

2. 异步操作

在 Store 中定义异步 Action:

type UserStore = {
  user: User | null;
  fetchUser: (id: string) => Promise<void>;
};

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  fetchUser: async (id) => {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    set({ user });
  },
}));

3. 中间件(Middleware)

持久化存储(Persist)

import { persist } from 'zustand/middleware';

export const useAuthStore = create(
  persist(
    (set) => ({
      token: null,
      login: (token) => set({ token }),
      logout: () => set({ token: null }),
    }),
    { name: 'auth-storage' } // 存储到 localStorage
  )
);

不可变更新(Immer)

import { immer } from 'zustand/middleware/immer';

export const useTodoStore = create(
  immer((set) => ({
    todos: [],
    addTodo: (text) =>
      set((state) => {
        state.todos.push({ text, completed: false }); // 直接修改 draft
      }),
  }))
);

四、最佳实践

1. 合理拆分 Store

  • 按功能模块拆分:避免单一 Store 过于臃肿。

    // store/userStore.ts
    export const useUserStore = create(...);
    
    // store/cartStore.ts
    export const useCartStore = create(...);

2. 类型安全(TypeScript)

为 Store 和 Actions 定义明确类型:

type UserState = {
  user: User | null;
  setUser: (user: User) => void;
  clearUser: () => void;
};

export const useUserStore = create<UserState>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  clearUser: () => set({ user: null }),
}));

3. 性能优化

  • 使用选择器:避免订阅无关状态。

  • 结合 shallow 比较:优化对象/数组类型的状态订阅。

    import { shallow } from 'zustand/shallow';
    
    const { user, setUser } = useUserStore(
      (state) => ({ user: state.user, setUser: state.setUser }),
      shallow
    );

4. 在组件外访问 Store

直接调用 Store 方法(如 API 模块中):

// 在非组件代码中
import { useCounterStore } from './store/counterStore';

const { increment } = useCounterStore.getState();
increment();

五、与 Redux 和 Context API 对比

特性ZustandReduxContext API
复杂度极低高(需 Action、Reducer、Middleware)
性能按需订阅状态,自动优化需手动优化(如 reselect全子树渲染,需手动优化
中间件/插件支持(持久化、Immer 等)丰富(Redux Thunk、Saga 等)
适用场景中小型应用,快速开发大型应用,需严格架构简单状态共享,低频更新
TypeScript天然支持需额外配置支持

六、常见问题与解决方案

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

  • 原因:组件未订阅相关状态或选择器未正确更新。

  • 解决:检查选择器逻辑,确保状态更新触发组件重渲染。

2. 循环依赖问题

  • 场景:多个 Store 相互依赖。

  • 解决:通过 getState 在 Action 中访问其他 Store:

    // store/authStore.ts
    import { useCartStore } from './cartStore';
    
    export const useAuthStore = create((set) => ({
      login: (user) => {
        const { clearCart } = useCartStore.getState();
        clearCart(); // 登录时清空购物车
        set({ user });
      },
    }));

3. Zustand 与 Next.js 服务端渲染(SSR)

  • 问题:服务端与客户端状态不一致。

  • 解决:使用 persist 中间件 + 自定义存储(如 Cookie):

    import { persist, createJSONStorage } from 'zustand/middleware';
    
    export const useStore = create(
      persist(
        (set) => ({ ... }),
        {
          name: 'store',
          storage: createJSONStorage(() => localStorage), // 或自定义存储
        }
      )
    );

七、总结

  • 核心优势:简洁、高性能、脱离组件树、中间件支持。

  • 适用场景:中小型 React 应用、需快速开发、状态共享与优化。

  • 最佳实践

    • 按功能拆分 Store。

    • 使用 TypeScript 确保类型安全。

    • 合理使用选择器和中间件优化性能。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值