Back

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

理解 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。对于更简单的应用程序或当你需要共享不经常变化的值时,使用 Context。

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

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

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

是的。虽然像 useReducer 和 useContext 这样的钩子可以处理更简单的状态管理需求,但 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