12k
All articles

理解 React 中的 Redux:像专业人士一样管理状态

Redux 通过集中式 store 简化 React 状态管理,actions、reducers 与 Redux Toolkit 协同实现可预测的状态更新。

OpenReplay Team
OpenReplay Team
理解 React 中的 Redux:像专业人士一样管理状态

你是否在 React 应用程序中跨多个组件管理状态时遇到困难?随着应用程序的增长,通过多层组件传递 props(prop 钻取)变得笨重且容易出错。Redux 通过提供一个集中式的状态管理系统来解决这个问题,使你的 React 应用程序更加可预测且更易于调试。

在这份全面指南中,你将了解 Redux 如何与 React 协同工作,何时使用它,以及如何在项目中有效地实施它。我们将涵盖从核心概念到使用 Redux Toolkit(编写 Redux 代码的现代方式)的实际实现的所有内容。

关键要点

  • Redux 为 React 应用程序提供集中式状态管理
  • 当你的应用在许多组件之间共享复杂状态时,使用 Redux
  • Redux 遵循单向数据流:actions → reducers → store → UI
  • Redux Toolkit 通过减少样板代码简化 Redux 开发
  • 性能优化对于较大的 Redux 应用程序至关重要

React 中的 Redux 是什么?

Redux 是一个用于 JavaScript 应用程序的可预测状态容器,在 React 中特别流行。它将应用程序的整个状态存储在一个名为""store""的单一不可变对象中。这种集中式的状态管理方法解决了 React 应用程序中的许多常见问题:

  • Prop 钻取:不再需要通过多层组件传递状态
  • 状态同步:确保组件之间的状态一致
  • 可预测的更新:状态变化遵循严格的单向数据流
  • 调试:使状态变化可跟踪和可预测

Redux 本身不是 React 的一部分,但通过 React Redux(React 的官方 Redux UI 绑定库)作为补充库工作。

// Basic Redux integration with React
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <YourApplication />
    </Provider>
  );
}

什么时候应该使用 Redux?

并非每个 React 应用程序都需要 Redux。考虑在以下情况使用 Redux:

  • 你的应用有在多个组件之间共享的复杂状态逻辑
  • 组件需要从组件树的不同部分访问和更新状态
  • 你需要跟踪和调试整个应用程序中的状态变化
  • 你的应用有中型到大型代码库,有多个开发人员

对于较小的应用程序或具有本地化状态的组件,React 内置的状态管理(useState、useReducer 和 Context API)通常就足够了。

Redux 核心概念

Store:单一真相来源

Redux store 保存应用程序的整个状态树。与分布在组件中的 React 组件状态不同,Redux 将所有状态集中在一个地方:

// Creating a Redux store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';

const store = configureStore({
  reducer: rootReducer
});

export default store;

Actions:描述状态变化

Actions 是描述应用程序中发生了什么的普通 JavaScript 对象。它们是向 Redux store 发送数据的唯一方式:

// An action object
{
  type: 'counter/incremented',
  payload: 1
}

// Action creator function
const increment = (amount) => {
  return {
    type: 'counter/incremented',
    payload: amount
  }
}

每个 action 必须有一个描述其类型的 type 属性。payload 属性包含 action 所需的任何数据。

Reducers:用于状态更新的纯函数

Reducers 是接收当前状态和一个 action,然后返回新状态的纯函数。它们指定应用程序的状态如何响应 actions 而变化:

// A simple reducer function
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'counter/incremented':
      return state + action.payload;
    case 'counter/decremented':
      return state - action.payload;
    default:
      return state;
  }
}

Reducers 必须:

  • 是纯函数(无副作用)
  • 永不直接修改状态
  • 当发生变化时返回一个新的状态对象

单向数据流

Redux 遵循严格的单向数据流:

  1. 你从组件中分发一个 action
  2. Redux store 将 action 传递给 reducer
  3. Reducer 基于 action 创建一个新状态
  4. Store 更新其状态并通知所有连接的组件
  5. 组件使用新状态重新渲染

这种单向流使状态变化可预测且更容易理解。

React Redux 集成

Provider:将 Redux 连接到 React

Provider 组件使 Redux store 对应用程序中的所有组件可用:

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

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

Hooks:在组件中访问 Redux

React Redux 提供了让组件与 Redux store 交互的钩子:

useSelector:读取状态

import { useSelector } from 'react-redux';

function CounterDisplay() {
  // Select the counter value from the store
  const count = useSelector(state => state.counter.value);
  
  return <div>Current count: {count}</div>;
}

useSelector 钩子:

  • 接受一个从 store 状态中提取数据的选择器函数
  • 当选择的状态变化时重新渲染组件
  • 通过避免不必要的重新渲染来优化性能

useDispatch:更新状态

import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

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

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

useDispatch 钩子:

  • 返回 store 的 dispatch 函数
  • 让你从任何组件分发 actions

使用 Redux Toolkit 的现代 Redux

Redux Toolkit 是编写 Redux 逻辑的官方推荐方式。它通过以下方式简化 Redux 开发:

  • 减少样板代码
  • 包含常见模式的有用工具
  • 为 store 设置提供良好的默认值
  • 在 reducers 中启用直接状态修改(通过 Immer)

使用 configureStore 创建 Store

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todosReducer from './features/todos/todosSlice';

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

export default store;

使用 createSlice 定义状态逻辑

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write ""mutating"" logic in reducers
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

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

使用 createAsyncThunk 处理异步操作

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Create an async thunk for fetching data
export const fetchUserData = createAsyncThunk(
  'users/fetchUserData',
  async (userId, thunkAPI) => {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    return await response.json();
  }
);

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

export default userSlice.reducer;

实际实现示例

让我们使用 Redux Toolkit 构建一个简单的计数器应用程序:

1. 设置 store

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

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

2. 为计数器功能创建一个 slice

// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

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

// Selector
export const selectCount = (state) => state.counter.value;

3. 将 store 连接到 React

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

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

4. 在组件中使用 Redux

// features/counter/Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  increment,
  decrement,
  incrementByAmount,
  selectCount
} from './counterSlice';

export function Counter() {
  const count = useSelector(selectCount);
  const dispatch = useDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  return (
    <div>
      <div>
        <button onClick={() => dispatch(decrement())}>-</button>
        <span>{count}</span>
        <button onClick={() => dispatch(increment())}>+</button>
      </div>
      <div>
        <input
          value={incrementAmount}
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          onClick={() => 
            dispatch(incrementByAmount(Number(incrementAmount) || 0))
          }
        >
          Add Amount
        </button>
      </div>
    </div>
  );
}

性能优化技术

使用 Reselect 的记忆化选择器

Reselect(包含在 Redux Toolkit 中)让你创建记忆化的选择器函数,只有在输入改变时才重新计算结果:

import { createSelector } from '@reduxjs/toolkit';

// Basic selectors
const selectItems = state => state.items;
const selectFilter = state => state.filter;

// Memoized selector
export const selectFilteredItems = createSelector(
  [selectItems, selectFilter],
  (items, filter) => {
    // This calculation only runs when items or filter changes
    return items.filter(item => item.includes(filter));
  }
);

避免不必要的渲染

为防止组件在状态的不相关部分更改时重新渲染:

  1. 只选择组件需要的特定数据
  2. 对派生数据使用记忆化选择器
  3. 在选择对象时使用 shallowEqual 函数作为 useSelector 的第二个参数
import { useSelector, shallowEqual } from 'react-redux';

// This component will only re-render when user.name or user.email changes
function UserInfo() {
  const { name, email } = useSelector(state => ({
    name: state.user.name,
    email: state.user.email
  }), shallowEqual);
  
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}

Redux 与其他状态管理解决方案的比较

Redux vs. Context API

Context API:

  • 内置于 React
  • 对小型到中型应用程序更简单
  • 不需要额外的库
  • 更少的样板代码
  • 有限的调试工具

Redux:

  • 对复杂状态逻辑更强大
  • 对频繁更新有更好的性能
  • 使用 Redux DevTools 进行出色的调试
  • 支持中间件处理副作用
  • 更结构化的状态管理方法

Redux vs. Zustand/Recoil/MobX

在以下情况下考虑这些替代方案:

  • 你需要一个更简单的 API,减少样板代码(Zustand)
  • 你想要基于原子的状态管理(Recoil)
  • 你更喜欢更面向对象、响应式的方法(MobX)

当你需要以下内容时,Redux 仍然是最佳选择:

  • 成熟、经过实战检验的解决方案
  • 出色的调试能力
  • 中间件和扩展的大型生态系统
  • 复杂应用程序的可预测状态管理

结论

Redux 为 React 应用程序提供了强大且可预测的状态管理解决方案。虽然它引入了一些复杂性和样板代码,但 Redux Toolkit 显著简化了开发体验。对于在多个组件之间共享状态的复杂应用程序,Redux 在可维护性、调试和可预测性方面提供了明显的好处。

在选择状态管理解决方案时,请仔细考虑应用程序的需求。对于更简单的应用程序,React 内置的状态管理可能就足够了,但随着应用程序复杂性的增加,Redux 的结构化方法变得越来越有价值。

常见问题

什么时候应该使用 Redux 而不是 React Context?

对于复杂的应用程序,特别是有频繁更新和共享状态的应用程序,使用 Redux。对于更简单的应用程序或当你需要共享不经常变化的值时,使用 Context。

Redux 难学吗?

Redux 有一些可能需要时间理解的核心概念,但 Redux Toolkit 显著简化了学习曲线。从小例子开始,逐渐增加复杂性。

Redux 会使我的应用变慢吗?

不一定。虽然 Redux 增加了一些开销,但它包含性能优化,实际上可以通过防止不必要的重新渲染来提高性能。

如何在 Redux 中处理异步操作?

使用中间件如 Redux Thunk(包含在 Redux Toolkit 中)或对于更复杂的异步流程使用 Redux Saga。Redux Toolkit 的 createAsyncThunk 使异步操作变得简单明了。

随着 React hooks 的出现,Redux 还相关吗?

是的。虽然像 useReducer 和 useContext 这样的钩子可以处理更简单的状态管理需求,但 Redux 在管理复杂的应用程序状态方面仍然表现出色,特别是在大型团队中。

如何调试 Redux 应用程序?

使用 Redux DevTools 扩展来检查状态、actions 和随时间的状态变化。这是 Redux 最强大的功能之一。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.