深入理解 React 状态管理——Redux (ˉ﹃ˉ)

0 阅读9分钟

在 React 开发的广阔天地里,状态管理堪称构建复杂应用的 “顶梁柱”。当应用规模逐渐扩大,组件间的状态传递变得错综复杂,传统的状态管理方式就显得力不从心。Redux 作为 React 中最常用的集中式状态管理工具,如同一位 “管理大师”,凭借清晰的数据流和模块化设计,帮助开发者轻松应对复杂状态管理难题。即便不依赖特定框架,Redux 也能独立运行,就像 Vue 中的 Pinia(Vuex)一样,在状态管理领域占据重要地位。接下来,我们就深入探索 Redux 的奥秘。

一、Redux 核心概念:状态管理的基石

Redux 把整个数据修改的流程归纳为三个核心概念:state、action、reducer,它们是 Redux 运行的基石,共同构建起有序的状态管理体系。

  • state:它是管理的数据初始状态,作为应用的唯一数据源,以对象的形式存储着整个应用的所有状态数据。比如在计数器应用里,count 值就是状态的一部分;在用户信息管理模块中,userProfile 也属于状态数据。并且,state 有着严格的 “规矩”—— 不可变性,任何对它的修改都必须返回一个全新的状态对象,这样一来,状态的每一次变化都清晰可追踪,方便开发者进行调试和维护。
  • action:本质上是一个普通对象,它的使命是标记当前想要对状态做什么样的修改。每个 action 都必须携带一个 type 字段,这是它的 “身份标识”,用于明确告知 reducer 具体的操作类型,例如 'INCREMENT' 表示执行增加操作。此外,payload 字段是可选的,用于传递一些额外的数据,以满足更复杂的状态更新需求。
  • reducer:作为一个纯函数,reducer 专门负责根据接收到的 action 来更新 state。它接收两个参数,分别是当前状态 state 和动作 action。reducer 会仔细 “查看” action.type,根据不同的类型判断该如何修改状态,最终返回一个全新的状态对象。需要特别注意的是,reducer 必须保持 “纯净”,坚决禁止直接修改原始状态,也不能执行任何异步操作,以确保状态更新的确定性和可预测性。

image.png

二、Redux 基础使用:从原生实现看运行流程

为了更直观地理解 Redux 的工作原理,我们先通过原生 Redux 实现一个简单的计数器示例,来剖析其核心使用步骤和运行流程。

原生 Redux 实现计数器

<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
<script src="https://cdn.jsdelivr.net/npm/redux@latest/dist/redux.min.js"></script>
<script>
  // 1. 定义 Reducer:处理状态更新逻辑
  function reducer(state = { count: 0 }, action) {
    if (action.type === 'INCREMENT') {
      return { count: state.count + 1 };
    } else if (action.type === 'DECREMENT') {
      return { count: state.count - 1 };
    }
    return state;
  }
  // 2. 创建 Store:传入 Reducer 生成状态容器
  const store = Redux.createStore(reducer);
  // 3. 订阅状态变化:数据更新时触发视图渲染
  store.subscribe(() => {
    console.log('数据变化了', store.getState());
    document.getElementById('count').innerText = store.getState().count;
  });
  // 4. 派发 Action:触发状态更新
  const inBtn = document.getElementById('increment');
  inBtn.addEventListener('click', () => {
    store.dispatch({ type: 'INCREMENT' });
  });
  const dBtn = document.getElementById('decrement');
  dBtn.addEventListener('click', () => {
    store.dispatch({ type: 'DECREMENT' });
  });
  // 5. 使用 store 实例对象的 getState 方法获取最新的状态数据更新到视图中
</script>

video.gif

核心流程详细解读

  1. 定义 reducer 函数:这是状态更新的 “指挥官”。我们定义的 reducer 函数接收当前状态 state 和动作 action 作为参数。初始状态设置为 { count: 0 },根据 action.type 的不同,返回对应的新状态。比如当 action.type 为 'INCREMENT' 时,就返回 { count: state.count + 1 },实现计数器加 1 的操作。
  1. 创建 store 实例对象:通过 Redux.createStore(reducer) 这行代码,将定义好的 reducer 函数传入,生成一个 store 实例对象。这个 store 就像是一个 “大仓库”,负责存储和管理整个应用的状态。
  1. 订阅数据变化:使用 store 实例对象的 subscribe 方法,传入一个回调函数。一旦状态发生变化,这个回调函数就会被触发。在回调函数中,我们通过 store.getState() 获取最新的状态数据,并将其更新到视图中,比如这里将计数器的值显示在页面的 span 元素里。
  1. 提交 action 对象触发数据变化:为页面上的按钮添加点击事件监听器,当按钮被点击时,通过 store.dispatch 方法提交 action 对象。例如点击 “+” 按钮,就会派发 { type: 'INCREMENT' } 这个 action,通知 reducer 函数执行相应的状态更新操作。
  1. 获取最新状态更新视图:在 subscribe 的回调函数中,已经实现了使用 store.getState() 获取最新状态数据并更新视图的功能,确保页面展示的状态始终是最新的。

三、React 中使用 Redux:工具库助力高效开发

虽然原生 Redux 能实现状态管理,但存在大量样板代码,开发起来较为繁琐。而 Redux Toolkit(RTK)React Redux 这两个强大的工具库,极大地简化了在 React 中使用 Redux 的流程。

1. 安装与项目结构搭建

首先,通过以下命令安装所需依赖:

npm install @reduxjs/toolkit react-redux

安装完成后,典型的项目结构如下:

src/
  store/
    index.js         # 创建 Store 入口
    modules/
      counterStore.js # 计数器模块
      channelStore.js # 频道模块
  App.js            # 根组件
  index.js          # 应用入口

这样的结构清晰地划分了不同功能模块,方便后续的开发和维护。

2. 使用 Redux Toolkit 构建模块

以计数器模块 counterStore.js 为例,createSlice 方法让我们可以轻松生成 reducer 和 action 对象。

// src/store/modules/counterStore.js
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
  name: "counter",
  // 初始化state
  initialState: {
    count: 0
  },
  // 修改状态的方法,支持直接修改
  reducers: {
    increment: (state) => {
      state.count++;
    },
    decrement: (state) => {
      state.count--;
    }
  }
});
// 解构actionCreater函数
const { increment, decrement } = counterStore.actions;
//获取reducer
const reducer = counterStore.reducer;
//按需导出的方式导出actionCreater
export { increment, decrement }
//默认导出reducer
export default reducer;

createSlice 方法接收一个对象,其中 name 是模块的名称,initialState 定义了初始状态,reducers 里的方法会自动生成对应的 action creator。通过解构赋值,我们导出了 increment 和 decrement 这两个 action creator,以及 reducer 函数,为后续在组件中使用做好准备。

3. 配置 Store 并连接 React 应用

在 store/index.js 中,使用 configureStore 方法来组合多个 reducer,创建最终的 store 实例。

// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块的reducer
import counterReducer from "./modules/counterStore";
const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});
export default store;

这里将 counterReducer 与 counter 键关联起来,后续在组件中就可以通过 state.counter 来访问计数器相关的状态。

在应用入口 index.js 中,借助 Provider 组件将 store 注入到整个 React 应用中,使所有组件都能访问到状态数据。

// 项目的入口,从这里开始运行
// React必要的核心包
import React from'react';
import ReactDOM from'react-dom/client';
import store from './store'
import { Provider } from'react-redux'
// 引入App组件,App组件是整个项目的根组件
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // react-redux提供的Provider组件,用于将store传递给整个应用
  <Provider store={store}>
    <App />
  </Provider>
);

4. 在组件中使用状态与操作

在 App.js 组件中,通过 useSelector 和 useDispatch 这两个钩子函数,实现对状态的获取和修改。

// 在React组件中使用store中的数据,需要用到一个钩子函数:useSelector,他的作用是从store中获取数据
import { useSelector } from "react-redux"
// 在React组件中修改store中的数据,需要用到一个钩子函数:useDispatch,他的作用是从store中获取dispatch函数
import { useDispatch } from "react-redux"
import { increment } from "./store/modules/counterStore"
import { decrement } from "./store/modules/counterStore"
import { useEffect } from "react"
import { fetchChannelList } from "./store/modules/channelStore"
function App() {
  const { count } = useSelector((state) => state.counter)
  const dispatch = useDispatch()
  //使用useEffect触发异步请求
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])
  return (
    <div className="App">
      <button onClick={() => dispatch(increment())}>+</button>
      {count}
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  )
}
export default App;

useSelector 钩子函数接收一个回调函数,通过 state.counter 精准获取计数器的状态。useDispatch 钩子函数则获取 dispatch 函数,用于在组件中派发 action,比如点击按钮时调用 dispatch(increment()) 来增加计数器的值。

四、异步操作处理:遵循样板代码规范

在 Redux 中处理异步操作(如 API 请求)时,我们可以按照特定的样板代码模式来实现。以 channelStore.js 为例,具体步骤如下:

  1. 创建 store 的写法保持不变,配置好同步修改方法:在 channelStore.js 中,先使用 createSlice 方法创建切片,定义好初始状态和同步的状态修改方法,如 setChannels。
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const channelStroe = createSlice({
  name: "channel",
  initialState: {
    channelList: []
  },
  reducers: {
    setChannels(state, action) {
      state.channelList = action.payload;
    },
  },
});

2. 单独封装一个函数,在函数内部 return 一个新函数,在新函数中

    • 封装异步请求获取数据:使用 axios 等库发送 API 请求,获取所需数据。
    • 调用同步 actionCreater 传入异步数据生成一个 action 对象,并使用 dispatch 提交:将获取到的数据作为参数,调用同步的 action creator(如 setChannels),生成一个 action 对象,然后通过 dispatch 方法提交,从而更新状态。
// 异步请求部分
const { setChannels } = channelStroe.actions;
const fetchChannelList = () => {
  return async (dispatch, getState) => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    dispatch(setChannels(res.data.data.channels))
  }
}
export { fetchChannelList }
const reducer = channelStroe.reducer;
export default reducer;

3. 组件中 dispatch 的写法保持不变:在 App.js 组件中,依然使用 dispatch(fetchChannelList()) 来触发异步操作,就像触发同步 action 一样自然。

五、总结:Redux 的核心要点与学习方向

通过以上内容的学习,我们对 Redux 在 React 中的应用有了全面且深入的了解。Redux 通过 state、action、reducer 三大核心概念,构建起一套严谨的状态管理体系,实现了状态的集中管理和有序更新。

在实际开发中,Redux Toolkit 和 React Redux 这两个工具库是我们的得力助手,它们简化了开发流程,减少了样板代码的编写。而异步操作处理的样板代码模式,为我们处理复杂的异步任务提供了清晰的思路和规范。

对于初学者来说,建议多动手实践,通过不同的案例加深对 Redux 各个环节的理解;在实际项目中,按照规范的项目结构进行开发,逐步积累经验,提升自己在复杂应用状态管理方面的能力。相信随着不断地学习和实践,你一定能熟练掌握 Redux,打造出高效、稳定的 React 应用。