Date: November 19, 2023
Sum: Redux基础、Redux工具、调试、美团案例
Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
为什么要使用Redux?
需求:不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器
使用步骤:
代码实现:
<button id="decrement">-button>
<span id="count">0span>
<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>
Redux的难点是理解它对于数据修改的规则, 下图动态展示了在整个数据的修改中,数据的流向
为了职责清晰,Redux代码被分为三个核心的概念,我们学redux,其实就是学这三个核心概念之间的配合,三个概念分别是:
Redux虽然是一个框架无关可以独立运行的插件,但是社区通常还是把它与React绑定在一起使用,以一个计数器案例体验一下Redux + React 的基础使用
在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux
npx create-react-app react-redux
npm i @reduxjs/toolkit react-redux
npm run start
store
目录modules
目录,在内部编写业务分类的子storemodules/counterStore.js
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
store/index.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './modules/counterStore'
export default configureStore({
reducer: {
// 注册子模块
counter: counterReducer
}
})
react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立
src/index.js
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数据
)
在React组件中使用store中的数据,需要用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:
useSelector作用:把store中的数据映射到组件中
注:上面是组件counterStore,下面是store/index.js. 即根组件
src/App.js
import { useDispatch ,useSelector } from "react-redux"
import { increment, decrement, addToNum } from "./store/modules/counterStore1"
function App() {
const { count } = useSelector(state => state.counter )
const dispatch = useDispatch()
return (
{ count }
)
}
export default App
React组件中修改store中的数据需要借助另外一个hook函数 - useDispatch,它的作用是生成提交action对象的dispatch函数
dispatch作用:在组件中提交action对象,从而修改store中数据
使用样例如下:
需求:组件中有俩个按钮2
add to 10
和
add to 20
可以直接把count值修改到对应的数字,目标count值是在组件中传递过去的,需要在提交action的时候传递参数
实现方式:利用action传参实现
1-在reducers的同步修改方法中添加action对象参数
在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上
理解:这边直接理解 addToNum 接受的参数会被传递到 action.payload 即可
总结:
需求理解
实现步骤
创建store的写法保持不变,配置好同步修改状态的方法
单独封装一个函数,在函数内部return一个新函数,在新函数中
2.1 封装异步请求获取数据
2.2 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
组件中dispatch的写法保持不变
代码实现 > 测试接口地址: http://geek.itheima.net/v1_0/channels
modules/channelStore.js
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 = 'http://geek.itheima.net/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
src/App.js
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 (
{channelList.map(task => - {task.name}
)}
)
}
export default App
Redux官方提供了针对于Redux的调试工具,支持实时state信息展示,action提交信息查看等
工具处理:
基本开发思路:
使用 RTK(Redux Toolkit)来管理应用状态, 组件负责 数据渲染 和 dispatch action
git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git
npm i
npm run serve
npm run start
1- 编写store逻辑
// 编写store
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数据
// 省略部分代码
import { useDispatch, useSelector } from 'react-redux'
import { fetchFoodsList } from './store/modules/takeaway'
import { useEffect } from 'react'
const App = () => {
// 触发action执行
// 1. useDispatch -> dispatch 2. actionCreater导入进来 3.useEffect
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchFoodsList())
}, [dispatch])
return (
{/* 导航 */}
{/* 内容 */}
{/* 外卖商品列表 */}
{foodsList.map(item => {
return (
)
})}
{/* 购物车 */}
)
}
export default App
1- 编写store逻辑
// 编写store
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
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- 编写组件逻辑
const Menu = () => {
const { foodsList, activeIndex } = useSelector(state => state.foods)
const dispatch = useDispatch()
const menus = foodsList.map(item => ({ tag: item.tag, name: item.name }))
return (
)
}
image.png
{/* 外卖商品列表 */}
{foodsList.map((item, index) => {
return (
activeIndex === index &&
)
})}
1- 编写store逻辑
// 编写store
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
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
关键:
a.判断商品是否添加过
// 是否添加过?以action.payload.id去cartList中匹配 匹配到了 添加过
const item = state.cartList.find(item => item.id === action.payload.id)
2- 编写组件逻辑
{/* 添加商品 */}
dispatch(addCart({
id,
picture,
name,
unit,
description,
food_tag_list,
month_saled,
like_ratio_desc,
price,
tag,
count
}))}>
Bug修复:
修复count增加问题
image.png
实现思路
// 计算总价
const totalPrice = cartList.reduce((a, c) => a + c.price * c.count, 0)
{/* fill 添加fill类名购物车高亮*/}
{/* 购物车数量 */}
0 && 'fill')}>
{cartList.length > 0 && {cartList.length}}
拓展:
react中不存在computed计算属性,因为每次数值的变化都会引起组件的重新渲染
image.png
1-控制列表渲染
const Cart = () => {
return (
{/* 添加visible类名 div会显示出来 */}
{/* 购物车列表 */}
{cartList.map(item => {
return (
{item.name}
¥
{item.price}
{/* 数量组件 */}
)
})}
)
}
export default Cart
2- 购物车增减逻辑实现
// 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--
}
{/* 数量组件 */}
dispatch(increCount({ id: item.id }))}
onMinus={() => dispatch(decreCount({ id: item.id }))}
/>
3-清空购物车实现
// 清除购物车clearCart (state) { state.cartList = []}
购物车
dispatch(clearCart())}>
清空购物车
image.png
// 控制购物车打开关闭的状态
const [visible, setVisible] = useState(false)
const onShow = () => {
if (cartList.length > 0) {
setVisible(true)
}
}
{/* 遮罩层 添加visible类名可以显示出来 */}
setVisible(false)}
/>