Redux引入
Redux感觉像组件通信的中介
state存放被管理的状态
action是申请改哪些数据,传入什么参数
reducer是怎么修改数据
我的理解更像是action像一个储存方法的对象,reducer是具体的方法的实现,不同的方法实现也不一样
store是个仓库,存储应用状态的仓库
自己实现redux对状态的管理
首先进行初始化:
import { createSlice } from '@reduxjs/toolkit';createSlice({name: 'counter', // slice 的名称initialState: {count: 0, // 初始状态},
});
初始化是对slice的初始化,也就是切片,切片的名字叫counter,初始状态为{counter:0}
有了状态以后设置修改状态的方法
import { createSlice } from '@reduxjs/toolkit'
const counterStore=createSlice({//初始化name: 'counter',initialState: {count:0 },//编写修改数据的方法,同步方法,支持直接修改reducers: {increment(state) {state.count++},decrement(state) {state.count--}}
})
解构获取这两个方法:
//解构actionCreater函数
const { increment, decrement } = counterStore.actions
获取reducer:
//获取reducer
const reducer = counterStore.reducer
按需导出reducer和action creators,导出actioncreators是为了派发action,触发状态的更新
reducer在Redux store里处理action,更新状态
//按需导出actionCreater
export { increment, decrement }
//默认导出的方式导出reducer
export default reducer
然后在index.js文件里组合
configureStore是 Redux Toolkit 提供的一个函数,用于创建 Redux store
import { configureStore } from "@reduxjs/toolkit";
counter Reducer是从 ./modules/counterStore
文件中导入的 reducer
import { configureStore } from "@reduxjs/toolkit";
//导入子模块reducer
import counterReducer from './modules/counterStore'
这里的reducer是个对象,将多个子模块的reducer组合在一起
counter是模块的名称,counterReducer是模块的reducer
由configureStore创建Redux store,支持异步操作(不过这里写的都是同步)
import { configureStore } from "@reduxjs/toolkit";
//导入子模块reducer
import counterReducer from './modules/counterStore'
configureStore({reducer: {counter:counterReducer
}
})
然后导出store,在组件里使用
在组件里使用需要用中间件链接,这步叫为react注入store:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import reportWebVitals from './reportWebVitals';
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Provider } from 'react-redux';
import store from './store'const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode><Provider store={store}>{/* //注入 */}<App/></Provider></React.StrictMode>
)
要使用store里的数据,需要用到一个钩子函数useSelector
在App组件里调用useSelector
import { useSelector } from "react-redux";
function App() {const {count}=useSelector(state=>state.counter)return (<div className='App'>
{count}</div>)
}
export default App
然后在index.js引入App并调用就好了
一些注意事项
默认导出的文件应该用默认导入的文件引入:
//默认导出/导入
export default store
import store from './store'
这叫命名导出/导入:
//命名导出
const name = 'John';
function greet() {console.log('Hello!');
}export { name, greet }; // 导出多个内容
//命名导入
import { name, greet } from './module';
如果你认为你的代码和配置完全正确,但是还是报错(我就这样),那可能是webpack把错误缓存下来了,需要清理webpack:
//删除package-lock.json
rm -rf node_modules package-lock.json
//重新下载
npm install
//然后启动
npm start
react组件里修改store的数据
引入App.js
useDispatch是封装好的钩子,dispatch方法调用的时候把一个dispatch赋值给这个形参
import { useDispatch, useSelector } from "react-redux";
//导入actionCreater
import {increment,decrement} from './store/modules/counterStore'
function App() {const { count } = useSelector(state => state.counter)const dispatch=useDispatch()return (<div className='App'><button onClick={()=>dispatch(decrement())}>-</button>{count}<button onClick={()=>dispatch(increment())}>+</button></div>)
}
export default App
这样就可以控制数字的+-了
actions提交参数
修改reduer,将state.count更新为action.payload的值
reducers: {increment(state) {state.count++},decrement(state) {state.count--},addToNum(state,action) {state.count=action.payload}}
在App.js里添加一个按钮,并传入参数:
<button onClick={() => dispatch(addToNum(0))}>
异步状态
很遗憾我没有实现出来,因为远程axios一直失败,可能是远程的后端数据的服务器倒闭了
如果想实现可以在本地写一个json,但是我做了四个小时的实验实在撑不下去了
所以就这样吧
放一下代码:
//channelStore
import { createSlice } from '@reduxjs/toolkit'
//导入axiosimport axios from 'axios';
const channelStore= createSlice({name: 'channel',initialState: {channelList:[]},reducers: {setChannels(state, action) {state.channelList=action.payload}}
})
//异步请求部分
const {setChannels}=channelStore.actions
const fetchChannelList = () => {return async(dispatch) => {const res = await axios.get('https://geek.itheima.net/v1_0/channels').then(response => console.log(response.data)).catch(error => console.error("请求失败", error));dispatch(setChannels(res.data.data.channels))}
}
export { fetchChannelList }
const channelReducer = channelStore.reducer
export default channelReducer
//\src\store\index.js
import { configureStore } from "@reduxjs/toolkit";
//导入子模块reducer
import counterReducer from './modules/counterStore';
import channelReducer from './modules/counterStore';
const store= configureStore({reducer: {counter:counterReducer,channel:channelReducer
}})
export default store
//\src\App.jsx
import { useDispatch, useSelector } from "react-redux";
import { increment, decrement, addToNum } from './store/modules/counterStore'
import { fetchChannelList } from "./store/modules/channelStore";
import { useEffect } from 'react'
import { createSelector } from 'reselect'// 使用 createSelector 创建 memoized 选择器
const selectCount = createSelector(state => state.counter,counter => counter.count
);const selectChannelList = createSelector(state => state.channels, channels => channels?.channelList || [] // 确保不为空
);function App() {const count = useSelector(selectCount);const channelList = useSelector(selectChannelList);const dispatch = useDispatch();useEffect(() => {dispatch(fetchChannelList()); // 需要执行 fetchChannelList()}, [dispatch]);
//useEffect在App首次渲染的时候执行一次
//dispatch是ReduxStore里提供的方法,可以触发action,触发状态的更新return (<div className='App'><button onClick={() => dispatch(decrement())}>-</button><button onClick={() => dispatch(addToNum(0))}>点我置为0</button>{count}<button onClick={() => dispatch(increment())}>+</button><ul>{channelList.map(item => <li key={item.id}>{item.name}</li>)}//主要修改了这部分,把数据渲染出来</ul></div>);
}export default App;
在上面代码里最让人疑惑的是为什么dispatch会作为useEffect的依赖项,它是严格会改变的方法吗?
方法是函数,函数也可以做useEffect的依赖项,而react官方文档推荐useEffect里用到的所有外部变量都应该被作为依赖,确保useEffect能在变量变化时重新执行,如果 Redux 未来更新,导致dispatch变了,就可能导致useEffect访问的是过时的dispatch,可能引发 Bug。
像这样,会报警告👆
Redux调试-devtools
点击Chart可以看到这样一个图,我是因为没获取到后端数据,所以节点很少
正常来说会给你一个这样的状态管理图
Raw可以看展示的数据
点击这个的左右按键可以回到上一次或下次状态改变的情况,方便做一些大项目的数据调试
还有一些redux的其他重要方法,后面会学到,例如store.subscribe(listener)、store.getState()