使用create-react-app创建项目,最终的项目结构如下:
下面介绍各个文件
package.json
{
"name": "redux-toolkit-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^2.1.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-scripts": "5.0.1",
"redux": "^5.0.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^3.1.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
使用如下命令安装项目所需的依赖包
npm install
./src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
//配合自定义的connect函数
import { StoreContext } from './hoc';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//配合使用react-redux提供的connect函数
<Provider store={store}>
//配合使用自定义的高阶函数connect
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>
</Provider>
);
./src/app.js
import Profile from "./views/Profile";
import Home from "./views/Home";
import About from "./views/About";
import './style.css'
import { useSelector } from "react-redux";
function App(props) {
const {number}= useSelector(state =>{
return state.counter
})
return (
<div className="App">
<h2>
App count:{number}
</h2>
<div className="c1">
<Home/>
<Profile/>
<About />
</div>
</div>
);
}
export default App;
./src/style.css
.c1 {
display: flex;
}
.c1 >div {
flex: 1;
border: 1px dashed #F00;
padding: 10px;
}
./src/hoc
该文件夹实现自定义的高阶函数connect
StoreContext.js
import { createContext } from "react"
export const StoreContext= createContext()
customizeConnect.js
import React, { PureComponent } from 'react'
//import store from '../store' store与项目解耦,只需要传入StoreContext即可,和react-redux的Provider一致
import { StoreContext } from './StoreContext'
/*
connect 的参数
参数1:函数
参数2:函数
返回值:函数=》高阶组件
*/
export function connect(mapStateToProps,mapDispatchToProps){
return function(WrappedComponent){
class NewComponent extends PureComponent{
constructor(props,context){
super(props)
this.state=mapStateToProps(context.getState())
}
componentDidMount(){
this.unsubscribe = this.context.subscribe(()=>{
this.setState(mapStateToProps( this.context.getState()))
})
}
componentWillUnmount(){
this.unsubscribe()
}
render(){
const stateObj = mapStateToProps( this.context.getState())
const dispatchObj = mapDispatchToProps( this.context.dispatch)
return <WrappedComponent {...this.props}{...stateObj}{...dispatchObj} />
}
}
NewComponent.contextType= StoreContext
// NewComponent.contextType= {store:StoreContext}
return NewComponent
}
}
index.js
export {StoreContext} from './StoreContext'
export {connect} from './customizeConnect'
src\store\
该文件夹与store相关
index.js
创建store可以使用redux提供的createStore,也可以使用reduxjs/toolkit的configureStore
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './features/counter'
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import { thunk } from 'redux-thunk'
import peomReducer from './features/poem'
import {reduxlogger,customizethunk,applyMiddlewareCustomize} from './middleware'
// 调用configureStore默认使用了
// 1. redux-thunk中间件来支持异步action,
// 2. redux-devtools-extension来支持ReduxDevTools浏览器扩展,
// 3. redux-immutable-state-invariant来检测state的不可变性。
/* const store = configureStore({
reducer: {
counter: counterReducer,
peom: peomReducer
}
}) */
//调用createStore创建store
const reducer = combineReducers({
counter: counterReducer,
peom: peomReducer
})
/*
注释该行,测试自定义的thunk函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
*/
const store = createStore(reducer)
/*
不调用customizethunk函数,报错信息如下
Actions must be plain objects. Instead, the actual type was: 'function'.
You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
*/
/*
customizethunk(store)
reduxlogger(store)
测试自定义的applyMiddleware
*/
applyMiddlewareCustomize(store,reduxlogger,customizethunk)
export default store;
src\store\features
该文件夹创建两个不同的reducer/action
counter.js
实时数据的reducer和action,使用createSlice
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name:"counter",
initialState:{
number:99
},
reducers:{
addNumber(state,action){
console.log("counterSlice reducer",action.payload)
return {...state,number:state.number+action.payload}
},
subNumber(state,action){
console.log("counterSlice reducer",action.payload)
return {...state,number:state.number-action.payload}
}
}
})
export const {addNumber,subNumber}=counterSlice.actions;
export default counterSlice.reducer;
poem.js
异步请求的reducer和action
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
/* https://api.gugudata.com/text/chinesepoem/demo*/
//方式1
export const fetchPeomdataAction=createAsyncThunk("fetch/peomdata",async ()=>{
const res= await axios.get("https://api.gugudata.com/text/chinesepoem/demo")
return res.data
})
//方式2
export const getPeomdataAction=createAsyncThunk("fetch/peomdata",async (extraInfo,{dispatch,getState})=>{
//console.log('getPeomdataAction-------',extraInfo)
const res= await axios.get("https://api.gugudata.com/text/chinesepoem/demo")
dispatch(changePeom(res.data.Data))
return res.data
})
const peomSlice = createSlice({
name:"peom",
initialState:{
peom:[]
},
reducers:{
changePeom(state,{payload}){
// console.log("peomSlice reducer",payload)
state.peom=payload
}
},
extraReducers:builder =>{
//方式1
builder.addCase(fetchPeomdataAction.pending,(state)=>{
console.log("fetchPeomdataAction.pending")
}).addCase(fetchPeomdataAction.fulfilled,(state,{payload})=>{
// console.log("fetchPeomdataAction.fulfilled",payload)
state.peom=payload.Data
})
}
})
export const {changePeom}=peomSlice.actions;
export default peomSlice.reducer;
src\store\middleware
该文件夹实现自定的中间价
applyMiddlewareCustomize.js
自定义实现redux提供的applyMiddileware功能,applyMiddileware主要功能时执行传入的函数,每个函数的实参为store
function applyMiddlewareCustomize(store, ...fns) {
fns.forEach((fn) => {
fn(store)
})
}
export default applyMiddlewareCustomize;
customizethunk.js
自定义实现redux-thunk提供的thunk,处理为function的action
function customizethunk(store) {
//最外层的派发函数
const next = store.dispatch
function thunkAndDispatch(action) {
//真正派发action的代码,使用之前真正的dispatch
if (typeof action === 'function') {
//当派发的action依然为函数时,
console.log('------派发的action为函数', action)
action(store.dispatch,store.getState)
}else {
next(action)
}
}
//monkey patch:串改现有代码,对整体的执行逻辑进行修改
store.dispatch = thunkAndDispatch
}
export default customizethunk;
reduxlogger.js
每次派发action后,再执行dispatch前打印action,每次执行后打印state
function reduxlogger(store) {
const next = store.dispatch
function logAndDispatch(action) {
console.log('当前派发的action:', action)
//真正派发action的代码,使用之前真正的dispatch
next(action)
console.log('派发action结果:', store.getState())
}
//monkey patch:串改现有代码,对整体的执行逻辑进行修改
store.dispatch = logAndDispatch
}
export default reduxlogger;
index.js
将创建的中间件导出
import reduxlogger from "./reduxlogger";
import customizethunk from "./customizethunk";
import applyMiddlewareCustomize from "./applyMiddlewareCustomize";
export {
reduxlogger,
customizethunk,
applyMiddlewareCustomize
}
src\views
组件
About.js
使用自定义的connect高阶函数,以及counter reducer/action
import React, { Component } from 'react'
import { connect } from '../hoc' //使用自定义的connect函数
import { addNumber, subNumber } from '../store/features/counter'
class About extends Component {
addNumber(num) {
this.props.addNumberMap(num)
}
subNumber(num) {
this.props.subNumberMap(num)
}
render() {
const { number } = this.props
return (
<div>
<h3>About 计数器:{number}</h3>
<button onClick={e => this.addNumber(5)}>+5</button>
<button onClick={e => this.subNumber(2)}>-2</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
number: state.counter.number
}
}
const mapDispatchToProps = (dispatch) => ({
addNumberMap(num) {
dispatch(addNumber(num))
},
subNumberMap(num) {
dispatch(subNumber(num))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(About)
Home.jsx
使用react-redux提供 的connect函数,以及用axios派发function类型的action获取数据并存入store中
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
//import axios from 'axios'
import { addNumber, subNumber } from '../store/features/counter'
import {fetchPeomdataAction,getPeomdataAction} from '../store/features/poem'
//import store from '../store'
export class Home extends PureComponent {
componentDidMount() {
/* axios.get("https://api.gugudata.com/text/chinesepoem/demo").then(res => {
const peom = res.data.Data
store.dispatch(changePeom(peom))
console.log(peom)
}) */
this.props.fetchPeomdata()
}
addNumber(num) {
this.props.addNumberMap(num)
}
subNumber(num) {
this.props.subNumberMap(num)
}
render() {
const { number } = this.props
// console.log("++++++++", number)
return (
<div>
<h2>Home count:{number}</h2>
<button onClick={e => this.addNumber(5)}>+5</button>
<button onClick={e => this.subNumber(2)}>-2</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
number: state.counter.number
}
}
const mapDispatchToProps = (dispatch) => ({
addNumberMap(num) {
dispatch(addNumber(num))
},
subNumberMap(num) {
dispatch(subNumber(num))
},
fetchPeomdata(){
//方式1
//dispatch(fetchPeomdataAction())
//方式2 getPeomdataAction
dispatch(getPeomdataAction({name:"test"}))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Profile.js
使用函数式组件,并用hooks获取state和dispatch,并将axios获取的数据在组件中显示
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addNumber, subNumber } from '../store/features/counter';
function Profile(props) {
const { number } = useSelector(state => state.counter)
const { peom } = useSelector(state => state.peom)
const dispatch = useDispatch()
const addcounter = (num) => {
dispatch(addNumber(num))
}
const subcounter = (num) => {
dispatch(subNumber(num))
}
//console.log("Profile 获取诗词", peom)
return (
<div>
<h2> Profile counter:{number}</h2>
<button onClick={e => addcounter(5)}>+5</button>
<button onClick={e => subcounter(2)}>-2</button>
<div className='peom'>
<h2> Profile 诗词大会</h2>
{
peom?.map((item, index) => {
return (<div key={index}>
<h3>{item.Title}</h3>
<h4>{item.Author}</h4>
<h5>{item.Type}</h5>
<ul>
{item.Content.map((it, index) => {
return <li key={index}>{it}</li>
})}
</ul>
</div>
)
})
}
</div>
</div>
);
}
export default Profile;
程序运行结果