6.Redux状态管理
一、Redux 状态管理
1、Redux 基础介绍
2、Redux 基础使用
- redux和redux-devtools-extension 安装
redux-devtools-extension
扩展是redux
的调试工具 且注意redux-devtools-extension
现在的版本要求的 redux
版本是 ^3.1.0 || ^4.0.0
npm install redux@4.0.0
npm install --save redux-devtools-extension
- 创建
src/store/index.js
文件
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
function couterReducer(state = { count: 0 }, action) {
switch (action.type) {
case "inc":
return { count: state.count + 1 };
default:
return state;
}
}
// 创建store
const store = createStore(couterReducer, composeWithDevTools());
export default store;
- 创建
src/views/About/About.jsx
文件
import React from 'react'
import store from "../../store";
import { useState } from "react";
export default function About() {
const [count, setCount] = useState()
const handleClick = () => {
store.dispatch({ type: "inc" })
}
// 订阅store,更新count
store.subscribe(() => {
setCount(store.getState().count)
})
return (
<>
{count || 0}
<button onClick={handleClick}>+</button>
</>
)
}
3、Redux 进阶使用
react-redux简化redux的使用
- 安装react-redux
npm install react-redux
# 如果有依赖冲突,使用 --legacy-peer-deps 选项
npm install react-redux --legacy-peer-deps
这种方法会忽略对等依赖冲突,但可能会导致潜在的兼容性问题。
<Provider store={store}>
src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import { RouterProvider } from "react-router-dom";
import router from "./router/index";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router}></RouterProvider>
</Provider>
</React.StrictMode>
);
- useSelector和useDispatch
src/views/About/About.jsx
import React from 'react'
import { useSelector, useDispatch } from "react-redux";
export default function About() {
const count = useSelector((state) => state.count)
const handleClick = () => {
dispatch({
type: 'inc',
payload: 5,
})
}
const dispatch = useDispatch()
return (
<>
{count || 0}
<button onClick={handleClick}>+</button>
</>
)
}
src/store/index.js
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
function couterReducer(state = { count: 0 }, action) {
switch (action.type) {
case "inc":
return { count: state.count + action.payload };
default:
return state;
}
}
// 创建store
const store = createStore(couterReducer, composeWithDevTools());
export default store;
二、如何处理多个reducer函数及redux模块化
1、redux combineReducers方法,合并多个reducer
src/store/modules/counter.js
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case "counter/inc":
return { count: state.count + action.payload };
default:
return state;
}
}
export default counterReducer;
src/store/index.js
import { createStore, combineReducers } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import counterReducer from "./modules/counter";
// 创建store,combineReducers合并多个reducer
const store = createStore(
combineReducers({
counter: counterReducer
}),
composeWithDevTools()
);
export default store;
src/views/About/About.jsx
import React from 'react'
import { useSelector, useDispatch } from "react-redux";
export default function About() {
const count = useSelector((state) => state.counter.count)
const handleClick = () => {
dispatch({
type: 'counter/inc',
payload: 5,
})
}
const dispatch = useDispatch()
return (
<>
{count || 0}
<button onClick={handleClick}>+</button>
</>
)
}
2、redux-thunk中间件处理异步action
dispatch默认只支持对象字面量,通过redux-thunk中间件可以处理异步action和回调函数,通过applyMiddleware方法让中间件生效
- 安装redux-thunk
npm install redux-thunk
# 如果有依赖冲突,使用 --legacy-peer-deps 选项
npm install redux-thunk --legacy-peer-deps
src/store/index.js
import { createStore, combineReducers } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { applyMiddleware } from "redux";
import counterReducer from "./modules/counter";
// 创建store,combineReducers合并多个reducer
const store = createStore(
combineReducers({
counter: counterReducer
}),
composeWithDevTools(applyMiddleware())
);
export default store;
src/views/About/About.jsx
import React from 'react'
import { useSelector, useDispatch } from "react-redux";
export default function About() {
const count = useSelector((state) => state.counter.count)
const handleClick = () => {
dispatch((dispatch) => {
setTimeout(() => {
dispatch({
type: 'counter/inc',
payload: 5,
})
}, 2000);
})
}
const dispatch = useDispatch()
return (
<>
{count || 0}
<button onClick={handleClick}>+</button>
</>
)
}
三、redux-toolkit(RTK)改善redux使用体验
- 可以自动跟redux devtools结合,不需要下载模块进行生效
- 数据不需要再通过返回值进行修改,像vue一样可以直接修改
- 内置了redux-thunk这个异步插件
- 代码风格更好,采用选项式编写程序
1、Redux Toolkit 基础使用
- 安装redux-toolkit
npm i @reduxjs/toolkit
src/store/modules/counter.js
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
// 触发dispatch的命名空间
name: "counter",
// initialState 初始化共享状态
initialState: {
value: 0,
},
// 编写reducer方法
reducers: {
increment: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counter";
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import { RouterProvider } from "react-router-dom";
import router from "./router/index";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router}></RouterProvider>
</Provider>
</React.StrictMode>
);
reportWebVitals();
src/views/About/About.jsx
import React from 'react'
import { useSelector, useDispatch } from "react-redux";
export default function About() {
const dispatch = useDispatch()
const value = useSelector((state) => state.counter.value)
const handleClick = () => {
dispatch({
type: 'counter/increment',
payload: 2
})
}
return (
<>
{value || 0}
<button onClick={handleClick}>+</button>
</>
)
}
2、Redux Toolkit 如何处理异步任务
createAsyncThunk
方法创建异步任务extraReducers
配置额外的reducer
extraReducers
是 Redux Toolkit 提供的一个强大功能,用于在 createSlice 中定义额外的 reducer,以便处理在当前 slice 之外定义的 action 类型。它允许一个切片 reducer 响应和更新自己的状态,同时处理其他切片或模块生成的 action 类型
src/store/modules/counter.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
const initialState = {
value: 0,
};
export const counterTestAction = createAsyncThunk(
"counter/testAction",
async () => {
const res = await new Promise((resolve) => {
setTimeout(() => {
resolve("success");
}, 1000);
});
return res;
}
);
const counterSlice = createSlice({
name: "counter",
initialState: { ...initialState },
reducers: {
increment: (state, action) => {
state.value += action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(counterTestAction.fulfilled, (state, action) => {
console.log(
"counterTestAction.fulfilled 成功触发",
"方法一处理异步操作方式"
);
})
.addCase(counterTestAction.rejected, (state, action) => {
console.log("counterTestAction.rejected 失败触发");
});
},
});
export default counterSlice.reducer;
src/views/About/About.jsx
import React from 'react'
import { useSelector, useDispatch } from "react-redux";
import { counterTestAction } from "../../store/modules/counter";
export default function About() {
const dispatch = useDispatch()
const value = useSelector((state) => state.counter.value)
const handleClick = () => {
dispatch(counterTestAction()).then((res) => {
console.log('方法二处理异步操作方式', res)
})
}
return (
<>
{value || 0}
<button onClick={handleClick}>+</button>
</>
)
}
四、通过redux-persist持久化存储
- 安装redux-persist
npm i redux-persist
# 如果有依赖冲突,使用 --legacy-peer-deps 选项
npm install redux-persist --legacy-peer-deps
import { createRoot } from 'react-dom/client'
import { configureStore } from '@reduxjs/toolkit'
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { PersistGate } from 'redux-persist/integration/react'
import App from './App'
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
version: 1,
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
})
let persistor = persistStore(store)
const container = document.getElementById('root')
if (container) {
const root = createRoot(container)
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
)
} else {
throw new Error(
"Root element with ID 'root' was not found in the document. Ensure there is a corresponding HTML element with the ID 'root' in your HTML file.",
)
}