文章目录
- 一.Redux的基础用法
- 1.示例:普通网页中的Redux计步器
- 2.Redux管理数据的流程
- 3.配套工具和环境准备
- 3.1.配套工具
- 3.2.环境准备
- 4.示例:React项目中的Redux计步器
- 思路
- 步骤
- step1:创建子模块
- step2:导入子模块
- step3:注入store实例
- step4:React组件内使用store中的数据
- step5:在组件内修改store中的数据
- step6:优化---定义代参actionCreater来传参
- 5.示例:使用Redux管理异步状态操作
- 5.1.创建子模块
- 5.2.导入子模块
- 5.3.注入store实例
- 5.4.在组件中使用
- 二.Redux案例:某团外卖
- 1.开发前准备
- 2.渲染商品分类和商品列表
- 2.1.创建子模块takeaway
- 2.2.引入子模块
- 2.3.把store注入到组件中
- 2.4.在根组件App中使用和渲染数据
- 3.点击分类实现高亮
- 3.1.RTK管理新状态activeIndex
- 3.2.组件中:点击触发action更新activeIndex,动态控制激活类名
- 4.点击分类项,实现商品列表的切换显示
一.Redux的基础用法
Redux之于React,类似于Vuex或Pinia之于Vue
Redux可以独立于框架运行,作用是通过几种管理的方式管理应用的状态
1.示例:普通网页中的Redux计步器
由于Redux可以独立于React框架,因此可在网页中使用
步骤:
-step1:定义一个reducer函数
该函数根据当前想要做的修改返回一个新状态,新状态的作用是根据不同的Action对象,返回不同的新state(原则:状态不可变)
示例:function myReducer(state,action){}-step2:使用createStore方法中的reducer函数生成一个store实例对象
示例:const store=Redux.createStore(myReducer)-step3:使用store.subscribe方法,订阅数据的变化
(数据一旦变化就会得到通知)
示例:storesubscribe(()=>{})-step4:使用store.dispatch方法,提交action对象,触发数据变化
dispatch提交一个action来更新状态
示例:store.dispatch({type:'INCREMENT'})-step5:使用store.getState方法,获取最新的状态并更新到视图中
示例:const state=stor.getState()
完整代码:
<!DOCTYPE html>
<html>
<head><title>Redux计数器示例</title><!-- 引入Redux库 --><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.1/redux.min.js"></script>
</head>
<body><h1>Redux计数器</h1><div><button id="decrement">-</button><span id="counter-value">0</span><button id="increment">+</button></div><script>// 1.定义reducer函数function myReducer(state = { count: 0 }, action) {// 判断type:type的值是从action对象中获取的,而action对象是由dispatch函数生成的。if (action.type === 'INCREMENT') return { count: state.count + 1 };//增加:返回新的状态对象if (action.type === 'DECREMENT') return { count: state.count - 1 };}// 2.使用reducer函数生成store实例const store = Redux.createStore(myReducer);// 3.通过store实例的subscribe订阅数据变化store.subscribe(() => {//该回调会在每次store更新时被调用。// 5.通过store实例的getState方法获取当前状态值,并更新到页面上。const state = store.getState();// 更新页面上的计数器显示document.getElementById('counter-value').textContent = state.count;});// 4.通过store实例的dispatch函数提交action更改状态/*在Redux中修改数据的唯一方式是:通过dispatch提交一个action对象来更新状态*/document.getElementById('increment').addEventListener('click', () => {store.dispatch({ type: 'INCREMENT' });// 提交增加计数器的action对象});document.getElementById('decrement').addEventListener('click', () => {store.dispatch({ type: 'DECREMENT' });// 提交减少计数器的action对象});</script>
</body>
</html>
2.Redux管理数据的流程
出于职责清晰和数据流向明确的考量,在Redux中,把修改数据的流程分为三个核心概念:
state
对象:存放管理的数据状态action
对象:描述如何修改数据的reducer
函数:形参是state和action
,根据acton
对象的描述生成一个新的state
3.配套工具和环境准备
3.1.配套工具
Redux Toolkit
插件(RTK)
官方推荐的编写Redux逻辑的方式,是一套工具的集合,能简化书写方式
优点:简化store的配置方式内置immer支持可变式状态修改内置thunk更好的异步创建
react-dedux
插件
用来链接Redux和React组件的中间件
(Redux向React组件获取状态,React组件向Redux更新状态,都可以通过这个中间件)
3.2.环境准备
创建项目:npx create-react-app my-react-redux(项目名)
进入项目目录:cd my-react-redux
安装配套工具:npm i @reduxjs/toolkit react-redux(插件)
启动项目:npm run start
项目store目录:
src
├─store 用于集中状态管理
│ ├─modules 用于在内部编写业务分类的子store(应用通常有多个子store模块)
│ │ └─index.js 作用是组合modules中的所有子模块并在store中导入
4.示例:React项目中的Redux计步器
思路
- Redux store配置:配置
counterStore
模块,配置根store
并导入counterStore
模块 - React组件:注入
store(react-redux)
,使用和修改store
中的数据
步骤
step1:创建子模块
使用React Toolkit
的createSlice()方法
创建counterStore
子模块
//store/modules/counterStore.jsimport {createSlice} from "@reduxjs/toolkit";
const counterstore=createSlice({name:"counter",// 初始化stateinitialState:{//----类似于Vuex中的Storecount:0},// 定义reducer函数reducers:{//----类似于vuex中的mutationsincrement(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
step2:导入子模块
//src/store/index.jsimport { configureStore } from "@reduxjs/toolkit";
// 导入子模块的reducer
import counterReducer from "./modules/counterStore";
export const store = configureStore({reducer: {// 注册子模块的reducer函数counter: counterReducer,},
});
step3:注入store实例
react-redux
可以链接Redux和React,
其内置的Provider
组件可以通过store参数,把创建好的store实例注入到应用中,从而正式建立链接
//src/index.jsimport { Provider } from 'react-redux';
import { store } from './store';
....{/* 注入:将store注入到Provider组件的props中,然后包裹App组件。 */}<Provider store={store}><App /></Provider>
step4:React组件内使用store中的数据
钩子函数useSelector可以把store中的数据映射到组件中
//App.jsimport { useSelector } from "react-redux";
function App() {const { count } = useSelector((state) => state.counter);//来自src/store/index.js的reducer函数return (<div className="App"><h1>Counter: {count}</h1></div>);
}
export default App;
step5:在组件内修改store中的数据
钩子函数useDispatch可以生成dispatch函数,用于提交action对象
//App.js
// 导入actionCreater
import {increment,decrement} from "./store/modules/counterStore";{/* 增加按钮,触发 increment action */}
<button onClick={() => dispatch(increment())}>+</button>
{/* 显示计数器 */}
<h1>Counter: {count}</h1>
{/* 减少按钮,触发 decrement action */}
<button onClick={() => dispatch(decrement())}>-</button>
*子模块counterStore中,在reducers中定义的increment()
和decrement()
被称为actionCreater方法
step6:优化—定义代参actionCreater来传参
App组件新增"addTo10"和"addTo20"两个按钮,
步骤:
* 在reducers的同步修改方法中添加action对象参数
* 在调用actionCreater时传参
* 参数传到action对象的payload属性上
代码:
//counterStore.js
.....// 定义reducer函数reducers:{...addToNum(state,action){state.count+=action.payload}}
// 解构出actionCreater函数
const {increment,decrement,addToNum}=counterstore.actions
// 获取reducer函数
const reducer=counterstore.reducer
// 按需导出actionCreater函数
export {increment,decrement,addToNum}
// 默认导出reducer函数
export default reducer//App.js
import {increment,decrement,addToNum} from "./store/modules/counterStore";<button onClick={() => dispatch(addToNum(10))}>+10</button><button onClick={() => dispatch(addToNum(20))}>+20</button>
总结:
useSelector
:获取store中的数据useDispatch
:获取dispatch方法- 得到要提交的action对象:
dispatch(increment())//执行store模块中导出的actionCreater方法,即increment()方法
5.示例:使用Redux管理异步状态操作
5.1.创建子模块
//新建store/modules/channelStore.js//step1:创建子模块channelStore.js,代码不变
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";const channelstore=createSlice({name:'channel',initialState:{channelList:[]},reducers:{setChannel(state,action){state.channelList=action.payload}}
})/**异步请求部分 */
// step2:单独封装一个函数
const fetchChannelList=()=>{// const dispatch=useDispatch()// 在函数内部return一个新函数,// 在新函数中封装异步请求,// 并调用actionCreater方法传入异步数据生成一个action对象并使用dispatch方法提交return async (dispatch)=>{const res=await axios.get("https://geek.itheima.net/v1_0/channels")dispatch(setChannel(res.data.data.channels))console.log("res:",res.data.data.channels)}
}// 解构出actionCreater函数
const {setChannel}=channelstore.actions
// 按需导出actionCreater函数和异步请求函数
export {setChannel,fetchChannelList}// 获取reducer函数
const reducers=channelstore.reducer
// 默认导出reducer函数
export default reducers
5.2.导入子模块
//store/index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块的reducer
import counterReducer from "./modules/counterStore";
import channelReducer from "./modules/channelStore";
export const store = configureStore({reducer: {// 注册子模块的reudcer函数counter: counterReducer,channel: channelReducer,},
});
5.3.注入store实例
略,无新增代码
5.4.在组件中使用
//App.js
import { fetchChannelList } from "./store/modules/channelStore";
import { useEffect } from "react";function App(){// 使用 dispatch 来触发 actionconst dispatch = useDispatch();// 使用useEffect来触发异步请求执行useEffect(() => {// 触发 fetchChannelList actiondispatch(fetchChannelList());}, [dispatch]);return(<ul>{channelList.map((item) => (<li key={item.id}>{item.name}</li>))}</ul>)
}
二.Redux案例:某团外卖
功能:
* 1.商品列表和分类渲染* 2.添加商品* 3.购物车操作* 4.订单数量统计和高亮实现
思路:
使用Redux Toolkit(RTK)
来管理应用状态,
组件负责数据渲染和dispatch action
1.开发前准备
step1:从远程仓库克隆本地git clone http://git.itcast.cn/heimaqianduan/redux-meituan.git
step2:安装所有依赖npm i
step3:启动mock服务(内置json-server)npm run serve
step4:启动前端服务npm run start
2.渲染商品分类和商品列表
2.1.创建子模块takeaway
//store/modules/takeaway.js
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";const foodsStore = createSlice({name: "foods",initialState: { foodsList: [] },reducers: {setFoodsList(state, action) {state.foodsList = action.payload;}}
})// 解构createAction方法
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 { setFoodsList, fetchFoodsList }// 获取reducer
const reducer = foodsStore.reducer
// 默认导出
export default reducer
2.2.引入子模块
//src/store/index.js import { configureStore } from "@reduxjs/toolkit";
// 导入子模块的reducer
import foodsReducer from "./modules/takeaway";export default configureStore({reducer: {// 这里的key要和子模块的name一致foods: foodsReducer,},
});
2.3.把store注入到组件中
//index.jsimport { createRoot } from 'react-dom/client'
import {Provider} from 'react-redux'
import App from './App'
import store from './store'const root = createRoot(document.getElementById('root'))
root.render(// 注入:把store注入到Provider组件中,然后包裹App组件<Provider store={store}><App /></Provider>
)
2.4.在根组件App中使用和渲染数据
//App.js
import { useDispatch,useSelector } from 'react-redux'
import { useEffect } from 'react'
import { fetchFoodsList } from './store/modules/takeaway'//注释掉静态数据const foodsList=[]const App=()=>{/*触发action执行*/const dispatch=useDispatch()// 使用useEffect钩子,在组件加载完成后获取外卖商品列表useEffect(()=>{dispatch(fetchFoodsList())// 派发一个异步获取外卖商品列表的action},[dispatch])/*获取foodsList渲染数据列表*/// 使用useSelector钩子获取foodsList数据const {foodsList} = useSelector(state => state.foods)
}
3.点击分类实现高亮
即点击左边侧边栏中的分类获得高亮选中,右边的对应内容在页面中渲染
高亮和对应的内容如何绑定?
- 记录当前的点击项
activeIndex
- 动态控制类名,判断条件
activeIndex===index
步骤:
1.RTK管理新状态activeIndex
2.组件中点击触发action,更改activeIndex
3.动态控制激活类名显示
3.1.RTK管理新状态activeIndex
//store/modules/takeaway.jsconst foodsStore = createSlice({name: "foods",initialState: {foodsList: [] ,//菜单列表activeIndex: 0 // 当前激活的菜单索引},reducers: {.....// 更改当前激活的菜单索引changeActiveIndex(state, action) {state.activeIndex = action.payload;}}
})
// 解构createAction方法
const { setFoodsList ,changeActiveIndex} = foodsStore.actions
// 按需导出
export { fetchFoodsList, changeActiveIndex }
3.2.组件中:点击触发action更新activeIndex,动态控制激活类名
//src/components/menus/index.jsimport classNames from 'classnames'
import './index.scss'
import { useSelector, useDispatch } from 'react-redux'
import { useEffect } from 'react'
import { changeActiveIndex } from '../../store/modules/takeaway'const Menu = () => {// 从redux中获取数据
const { foodsList,activeIndex} = useSelector((state) => state.foods);
////过滤条件: 过滤出所有标签,并且去除重复的
const menus=foodsList.map(item=>({tag:item.tag,name:item.name}))
const dispatch = useDispatch()// 使用useEffect触发actionuseEffect(() => {dispatch(changeActiveIndex(0))//初始值让渲染有默认选中}, [dispatch])return (<nav className="list-menu">{/* 添加active类名会变成激活状态 */}{menus.map((item, index) => {return (<divonClick={() => {dispatch(changeActiveIndex(index))}}key={item.tag}className={classNames('list-menu-item',activeIndex===index&&'active')}>{item.name}</div>)})}</nav>)
}
export default Menu
4.点击分类项,实现商品列表的切换显示
条件渲染,控制对应项的显隐activeIndex===index&&<div></div>
//App.js
const { foodsList, activeIndex } = useSelector(state => state.foods)
....
{/* 外卖商品列表 */}
{foodsList.map((item, index) => {return (activeIndex === index && <FoodsCategorykey={item.tag}// 列表标题name={item.name}// 列表商品foods={item.foods}/>)
})}