React篇——第三章 状态管理之 Redux 篇

目录

[一、Redux 介绍](#一、Redux 介绍)

[为什么要使用 Redux?](#为什么要使用 Redux?)

[二、Redux 快速体验](#二、Redux 快速体验)

[1. 实现计数器](#1. 实现计数器)

[2. Redux 数据流架构](#2. Redux 数据流架构)

[三、Redux 与 React - 环境准备](#三、Redux 与 React - 环境准备)

[1. 配套工具](#1. 配套工具)

​编辑

[2. 配置基础环境](#2. 配置基础环境)

[3. store 目录结构设计](#3. store 目录结构设计)

[四、Redux 与 React - 实现 counter](#四、Redux 与 React - 实现 counter)

[1. 整体路径熟悉](#1. 整体路径熟悉)

[2. 使用 React Toolkit 创建 counterStore](#2. 使用 React Toolkit 创建 counterStore)

[3. 为 React 注入 store](#3. 为 React 注入 store)

[4. React 组件使用 store 中的数据](#4. React 组件使用 store 中的数据)

[5. React 组件修改 store 中的数据](#5. React 组件修改 store 中的数据)

[五、Redux 与 React - 提交 action 传参](#五、Redux 与 React - 提交 action 传参)

[六、Redux 与 React - 异步 action 处理](#六、Redux 与 React - 异步 action 处理)

[七、Redux 调试 - devtools](#七、Redux 调试 - devtools)

八、实战案例:购物车应用

[1. 案例演示](#1. 案例演示)

[2. 分类和商品列表渲染](#2. 分类和商品列表渲染)

[3. 点击分类激活交互实现](#3. 点击分类激活交互实现)

[4. 商品列表切换显示](#4. 商品列表切换显示)

[5. 添加购物车实现](#5. 添加购物车实现)

[6. 统计区域实现](#6. 统计区域实现)

[7. 购物车列表功能实现](#7. 购物车列表功能实现)

[8. 控制购物车显示和隐藏](#8. 控制购物车显示和隐藏)

九、总结

[Redux 核心概念回顾](#Redux 核心概念回顾)

[Redux Toolkit 优势](#Redux Toolkit 优势)

最佳实践

[什么时候使用 Redux](#什么时候使用 Redux)


Redux是一个独立于框架的集中状态管理工具,通过单向数据流管理应用状态。核心概念包括state(状态数据)、action(操作描述)和reducer(状态更新函数)。

使用Redux Toolkit可简化开发,提供createSlice创建模块化store,configureStore组合多个reducer。在React中结合react-redux使用,通过Provider注入store,useSelector获取状态,useDispatch提交action。

支持异步操作处理,通过中间件实现。Redux适用于中大型应用的状态共享,提供调试工具和清晰的架构,但简单场景可能更适合使用React原生状态管理。

购物车案例展示了Redux在实战中的应用,包括状态管理、异步请求和UI交互。

一、Redux 介绍

Redux 是 React 最常用的集中状态管理工具,类似于 Vue 中的 Pinia(Vuex),可以独立于框架运行。它通过集中管理的方式管理应用的状态,解决了复杂应用中组件间状态共享的问题。

为什么要使用 Redux?

  1. 独立于组件:无视组件之间的层级关系,简化组件间通信问题

  2. 单项数据流清晰:数据流单向流动,易于定位 bug

  3. 调试工具配套良好:提供了强大的调试工具,方便开发和调试

二、Redux 快速体验

1. 实现计数器

我们来实现一个不绑定任何框架、不使用任何构建工具的纯 Redux 计数器。

使用步骤:

  1. 定义一个 reducer 函数(根据当前想要做的修改返回一个新的状态)

  2. 使用 createStore 方法传入 reducer 函数生成一个 store 实例对象

  3. 使用 store 实例的 subscribe 方法订阅数据的变化(数据一旦变化,可以得到通知)

  4. 使用 store 实例的 dispatch 方法提交 action 对象触发数据变化(告诉 reducer 你想怎么改数据)

  5. 使用 store 实例的 getState 方法获取最新的状态数据更新到视图中

代码实现:

html 复制代码
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
​
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
​
<script>
  // 定义reducer函数 
  // 内部主要的工作是根据不同的action 返回不同的state
  function counterReducer (state = { count: 0 }, action) {
    switch (action.type) {
      case 'INCREMENT':
        return { count: state.count + 1 }
      case 'DECREMENT':
        return { count: state.count - 1 }
      default:
        return state
    }
  }
  // 使用reducer函数生成store实例
  const store = Redux.createStore(counterReducer)
​
  // 订阅数据变化
  store.subscribe(() => {
    console.log(store.getState())
    document.getElementById('count').innerText = store.getState().count
​
  })
  // 增
  const inBtn = document.getElementById('increment')
  inBtn.addEventListener('click', () => {
    store.dispatch({
      type: 'INCREMENT'
    })
  })
  // 减
  const dBtn = document.getElementById('decrement')
  dBtn.addEventListener('click', () => {
    store.dispatch({
      type: 'DECREMENT'
    })
  })
</script>

2. Redux 数据流架构

Redux 的难点是理解它对于数据修改的规则,下图动态展示了在整个数据的修改中,数据的流向。

为了职责清晰,Redux 代码被分为三个核心的概念,我们学 Redux,其实就是学这三个核心概念之间的配合:

  1. state:一个对象,存放着我们管理的数据

  2. action:一个对象,用来描述你想怎么改数据

  3. reducer:一个函数,根据 action 的描述更新 state

三、Redux 与 React - 环境准备

Redux 虽然是一个框架无关可以独立运行的插件,但是社区通常还是把它与 React 绑定在一起使用。我们以一个计数器案例体验一下 Redux + React 的基础使用。

1. 配套工具

在 React 中使用 Redux,官方要求安装两个其他插件:

  1. Redux Toolkit(RTK):官方推荐编写 Redux 逻辑的方式,是一套工具的集合,简化书写方式

  2. react-redux:用来链接 Redux 和 React 组件的中间件

2. 配置基础环境

1.使用 CRA 快速创建 React 项目

bash 复制代码
npx create-react-app react-redux 

2.安装配套工具

bash 复制代码
// 若已经创建好react项目,可直接使用此命令,下载redux等工具
npm i @reduxjs/toolkit react-redux 

3.启动项目

bash 复制代码
npm run start 

3. store 目录结构设计

  1. 通常集中状态管理的部分都会单独创建一个单独的 store 目录

  2. 应用通常会有很多个子 store 模块,所以创建一个 modules 目录,在内部编写业务分类的子 store

  3. store 中的入口文件 index.js 的作用是组合 modules 中所有的子模块,并导出 store

四、Redux 与 React - 实现 counter

1. 整体路径熟悉

2. 使用 React Toolkit 创建 counterStore

html 复制代码
import { createSlice } from '@reduxjs/toolkit'
​
const counterStore = createSlice({
  // 模块名称独一无二
  name: 'counter',
  // 初始数据
  initialState: {
    count: 1
  },
  // 修改数据的同步方法
  reducers: {
    increment (state) {
      state.count++
    },
    decrement(state){
      state.count--
    }
  }
})
// 结构出actionCreater
const { increment, decrement } = counterStore.actions
​
// 获取reducer函数
const counterReducer = counterStore.reducer
​
// 导出
export { increment, decrement }
export default counterReducer
import { configureStore } from '@reduxjs/toolkit'
​
import counterReducer from './modules/counterStore'
​
export default configureStore({
  reducer: {
    // 注册子模块
    counter: counterReducer
  }
})

3. 为 React 注入 store

react-redux 负责把 Redux 和 React 链接起来,内置 Provider 组件通过 store 参数把创建好的 store 实例注入到应用中,链接正式建立。

html 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'
​
ReactDOM.createRoot(document.getElementById('root')).render(
  // 提供store数据
  <Provider store={store}>
    <App />
  </Provider>
)

4. React 组件使用 store 中的数据

在 React 组件中使用 store 中的数据,需要用到一个钩子函数 - useSelector,它的作用是把 store 中的数据映射到组件中。

5. React 组件修改 store 中的数据

React 组件中修改 store 中的数据需要借助另外一个 hook 函数 - useDispatch,它的作用是生成提交 action 对象的 dispatch 函数。

五、Redux 与 React - 提交 action 传参

需求: 组件中有两个按钮 add to 10add to 20 可以直接把 count 值修改到对应的数字,目标 count 值是在组件中传递过去的,需要在提交 action 的时候传递参数。

实现方式: 在 reducers 的同步修改方法中添加 action 对象参数,在调用 actionCreater 的时候传递参数,参数会被传递到 action 对象 payload 属性上。

六、Redux 与 React - 异步 action 处理

需求理解:

实现步骤:

  1. 创建 store 的写法保持不变,配置好同步修改状态的方法

  2. 单独封装一个函数,在函数内部 return 一个新函数,在新函数中2.1 封装异步请求获取数据2.2 调用同步 actionCreater 传入异步数据生成一个 action 对象,并使用 dispatch 提交

  3. 组件中 dispatch 的写法保持不变

代码实现:

html 复制代码
import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'
​
const channelStore = createSlice({
  name: 'channel',
  initialState: {
    channelList: []
  },
  reducers: {
    setChannelList (state, action) {
      state.channelList = action.payload
    }
  }
})
​
​
// 创建异步
const { setChannelList } = channelStore.actions
const url = 'https://api.example.com/v1_0/channels'
// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get(url)
    dispatch(setChannelList(res.data.data.channels))
  }
}
​
export { fetchChannelList }
​
const channelReducer = channelStore.reducer
export default channelReducer
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchChannelList } from './store/channelStore'
​
function App () {
  // 使用数据
  const { channelList } = useSelector(state => state.channel)
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])
​
  return (
    <div className="App">
      <ul>
        {channelList.map(task => <li key={task.id}>{task.name}</li>)}
      </ul>
    </div>
  )
}
​
export default App

七、Redux 调试 - devtools

Redux 官方提供了针对 Redux 的调试工具,支持实时 state 信息展示,action 提交信息查看等。

八、实战案例:购物车应用

1. 案例演示

基本开发思路: 使用 RTK(Redux Toolkit)来管理应用状态,组件负责数据渲染和 dispatch action。

2. 分类和商品列表渲染

1- 编写 store 逻辑:

html 复制代码
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
​
const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 商品列表
    foodsList: []
  },
  reducers: {
    // 更改商品列表
    setFoodsList (state, action) {
      state.foodsList = action.payload
    }
  }
})
​
// 异步获取部分
const { setFoodsList } = foodsStore.actions
const fetchFoodsList = () => {
  return async (dispatch) => {
    // 编写异步逻辑
    const res = await axios.get('http://localhost:3004/takeaway')
    // 调用dispatch函数提交action
    dispatch(setFoodsList(res.data))
  }
}
​
export { fetchFoodsList }
​
const reducer = foodsStore.reducer
​
export default reducer

2- 组件使用 store 数据:

html 复制代码
import { useDispatch, useSelector } from 'react-redux'
import { fetchFoodsList } from './store/modules/takeaway'
import { useEffect } from 'react'
​
const App = () => {
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(fetchFoodsList())
  }, [dispatch])
​
  const { foodsList } = useSelector(state => state.foods)
​
  return (
    <div className="home">
      <NavBar />
      <div className="content-wrap">
        <div className="content">
          <Menu />
          <div className="list-content">
            <div className="goods-list">
              {foodsList.map(item => {
                return (
                  <FoodsCategory
                    key={item.tag}
                    name={item.name}
                    foods={item.foods}
                  />
                )
              })}
            </div>
          </div>
        </div>
      </div>
      <Cart />
    </div>
  )
}
​
export default App

3. 点击分类激活交互实现

1- 编写 store 逻辑:

html 复制代码
import { createSlice } from "@reduxjs/toolkit"
​
const foodsStore = createSlice({
  name: 'foods',
  initialState: {
    // 菜单激活下标值
    activeIndex: 0
  },
  reducers: {
    // 更改activeIndex
    changeActiveIndex (state, action) {
      state.activeIndex = action.payload
    }
  }
})
​
// 导出
const { changeActiveIndex } = foodsStore.actions
​
export { changeActiveIndex }
​
const reducer = foodsStore.reducer
​
export default reducer

2- 编写组件逻辑:

html 复制代码
const Menu = () => {
  const { foodsList, activeIndex } = useSelector(state => state.foods)
  const dispatch = useDispatch()
  const menus = foodsList.map(item => ({ tag: item.tag, name: item.name }))
  return (
    <nav className="list-menu">
      {menus.map((item, index) => {
      return (
        <div
          onClick={() => dispatch(changeActiveIndex(index))}
          key={item.tag}
          className={classNames(
            'list-menu-item',
            activeIndex === index && 'active'
          )}
          >
          {item.name}
        </div>
      )
    })}
    </nav>
  )
}

4. 商品列表切换显示

html 复制代码
<div className="list-content">
  <div className="goods-list">
    {foodsList.map((item, index) => {
      return (
        activeIndex === index && <FoodsCategory
          key={item.tag}
          name={item.name}
          foods={item.foods}
        />
      )
    })}
  </div>
</div>

5. 添加购物车实现

1- 编写 store 逻辑:

html 复制代码
import { createSlice } from "@reduxjs/toolkit"
​
const foodsStore = createSlice({
  name: 'foods',
  reducers: {
    // 添加购物车
    addCart (state, action) {
      // 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
      const item = state.cartList.find(item => item.id === action.payload.id)
      if (item) {
        item.count++
      } else {
        state.cartList.push(action.payload)
      }
    }
  }
})
​
// 导出actionCreater
const { addCart } = foodsStore.actions
​
export { addCart }
​
const reducer = foodsStore.reducer
​
export default reducer

2- 编写组件逻辑:

html 复制代码
<div className="goods-count">
  <span 
    className="plus" 
    onClick={() => dispatch(addCart({
    id,
    picture,
    name,
    unit,
    description,
    food_tag_list,
    month_saled,
    like_ratio_desc,
    price,
    tag,
    count
  }))}></span>
</div>

6. 统计区域实现

实现思路:

  1. 基于 store 中的 cartList 的 length 渲染数量

  2. 基于 store 中的 cartList 累加 price * count

  3. 购物车 cartList 的 length 不为零则高亮

html 复制代码
// 计算总价 
const totalPrice = cartList.reduce((a, c) => a + c.price * c.count, 0)
​
{/* fill 添加fill类名购物车高亮*/}
{/* 购物车数量 */}
<div onClick={onShow} className={classNames('icon', cartList.length > 0 && 'fill')}>
  {cartList.length > 0 && <div className="cartCornerMark">{cartList.length}</div>}
</div>

7. 购物车列表功能实现

1- 控制列表渲染:

html 复制代码
const Cart = () => {
  const { cartList } = useSelector(state => state.foods)
  return (
    <div className="cartContainer">
      <div className={classNames('cartPanel', 'visible')}>
        <div className="scrollArea">
          {cartList.map(item => {
            return (
              <div className="cartItem" key={item.id}>
                <img className="shopPic" src={item.picture} alt="" />
                <div className="main">
                  <div className="skuInfo">
                    <div className="name">{item.name}</div>
                  </div>
                  <div className="payableAmount">
                    <span className="yuan">¥</span>
                    <span className="price">{item.price}</span>
                  </div>
                </div>
                <div className="skuBtnWrapper btnGroup">
                  <Count
                    count={item.count}
                  />
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}
​
export default Cart

2- 购物车增减逻辑实现:

html 复制代码
// count增
increCount (state, action) {
  // 关键点:找到当前要修改谁的count id
  const item = state.cartList.find(item => item.id === action.payload.id)
  item.count++
},
// count减
decreCount (state, action) {
  // 关键点:找到当前要修改谁的count id
  const item = state.cartList.find(item => item.id === action.payload.id)
  if (item.count === 0) {
    return
  }
  item.count--
}
<div className="skuBtnWrapper btnGroup">
  <Count
    count={item.count}
    onPlus={() => dispatch(increCount({ id: item.id }))}
    onMinus={() => dispatch(decreCount({ id: item.id }))}
    />
</div>

3- 清空购物车实现:

html 复制代码
// 清除购物车
clearCart (state) {
  state.cartList = []
}
<div className="header">
  <span className="text">购物车</span>
  <span 
    className="clearCart" 
    onClick={() => dispatch(clearCart())}>
    清空购物车
  </span>
</div>

8. 控制购物车显示和隐藏

html 复制代码
// 控制购物车打开关闭的状态
const [visible, setVisible] = useState(false)
​
const onShow = () => {
  if (cartList.length > 0) {
    setVisible(true)
  }
}
​
{/* 遮罩层 添加visible类名可以显示出来 */}
<div
    className={
     classNames('cartOverlay', visible && 'visible')
  }
    onClick={() => setVisible(false)}
/>

九、总结

Redux 核心概念回顾

  1. State:应用的状态数据

  2. Action:描述要执行的操作的对象,包含 type 和可选的 payload

  3. Reducer:纯函数,根据当前 state 和 action 返回新的 state

  4. Store:将 state、action、reducer 结合在一起的对象

Redux Toolkit 优势

  1. 简化代码:自动处理不可变更新

  2. 减少样板代码:createSlice 和 configureStore 大大简化了代码

  3. 内置常用功能:包含了 Immer、Redux-Thunk 等常用库

  4. 类型安全:对 TypeScript 支持良好

最佳实践

  1. 使用 Redux Toolkit 而不是原生 Redux

  2. 按功能模块组织 store(feature-based slicing)

  3. 保持 reducer 纯净,不要在里面执行异步操作

  4. 使用 createAsyncThunk 处理异步逻辑

  5. 合理使用 useSelector 和 useDispatch

  6. 利用 Redux DevTools 进行调试

什么时候使用 Redux

  • 应用有大量的状态需要在多个组件间共享

  • 状态需要频繁更新

  • 状态更新逻辑复杂

  • 需要在组件树的不同层级间传递状态

Redux 是一个强大的状态管理工具,但也不是所有项目都需要。对于简单的应用,React 的 useState 和 useContext 可能就足够了。但对于中大型应用,Redux 可以帮助你更好地管理状态,提高代码的可维护性。

相关推荐
子兮曰2 小时前
🚀24k Star 的 Pretext 为何突然爆火:它不是排版库,而是在重写 Web 文本测量
前端·javascript·github
@大迁世界2 小时前
11.在 React.js 中,state 与 props 的差异体现在哪里?
前端·javascript·react.js·前端框架·ecmascript
Giant1002 小时前
🔥前端跨域封神解法:Vite Proxy + Express CORS,一篇搞定所有跨域坑!
前端·javascript·面试
用户3167361303422 小时前
SSE消息推送前后端代码
前端·后端
像我这样帅的人丶你还2 小时前
JavaScript 迭代器详解
前端·javascript
逍遥归来3 小时前
《SWIFTER -Swift开发者必备Tips》学习笔记
前端
timi先生3 小时前
语料库全栈项目部署 (Vue + Java + CQPweb)
java·前端·vue.js
Lazy_zheng3 小时前
Map / Set / WeakMap / WeakSet,一次给你讲透
前端·javascript·面试
learyuan3 小时前
Windows原生开发
前端