npm i @reduxjs/toolkit react-redux redux-persist
import {configureStore, combineReducers } from '@reduxjs/toolkit'
import CollapsedSlice from './features/CollapsedSlice'
import LoadingSlice from './features/LoadingSlice';
import AsyncReduxSlice from './features/AsyncReduxSlice';
import CreateAsyncThunkSlice from './features/CreateAsyncThunkSlice';
import ListFilterSlice from './features/ListFilterSlice';
import RouterListenerSlice from './features/RouterListenerSlice';
//持久化数据
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER
} from 'redux-persist'
import storage from 'redux-persist/lib/storage';
const reducer = combineReducers({
CollapsedSlice,//相当于CollapsedSlice:CollapsedSlice
LoadingSlice,
RouterListenerSlice,
AsyncReduxSlice,
CreateAsyncThunkSlice,
ListFilterSlice,
})
const persistConfig = {
key:'redux',
storage:storage,
whitelist:['CollapsedSlice'],//白名单只保存CollapsedSlice
// blacklist:['CollapsedSlice'],//黑名单仅不保存CollapsedSlice
}
const persistedRedcer = persistReducer(persistConfig,reducer);
const store = configureStore({
reducer:persistedRedcer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
//忽略了 Redux Persist 调度的所有操作类型。这样做是为了在浏览器控制台读取a non-serializable value was detected in the state时不会出现错误。
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
})
})
export const persistor = persistStore(store);
export default store;
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import "./axios";
import { Provider } from 'react-redux';
import store,{persistor} from './redux/store'
import { PersistGate } from "redux-persist/integration/react";
// import reportWebVitals from './reportWebVitals';
// npm i -g json-server
// json-server --watch ./db.json --port 5000
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
//
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();
使用createSlice方法创建一个slice。每一个slice里面包含了reducer和actions,可以实现模块化的封装。所有的相关操作都独立在一个文件中完成。
CollapsedSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
collapsed:false
}
export const collapsedSlice = createSlice({
// 命名空间,在调用action的时候会默认的设置为action的前缀collapsedSlice/changeCollapsed
name:'collapsedSlice',
// 初始值
initialState:initialState,
// 这里的属性会自动的导出为collapsedSlice.actions,在组件中可以直接通过dispatch进行触发
reducers:{
//{ payload }解构出来的payload是dispatch传递的数据对象
changeCollapsed(state,action){
// console.log(action)
// {
// "type":"collapsedSlice/changeCollapsed",
// "payload":{
// "value":2
// }
// }
// 内置了immutable不可变对象来管理state,不用再自己拷贝数据进行处理
state.collapsed = !state.collapsed;
}
}
})
// 导出actions
export const { changeCollapsed } = collapsedSlice.actions;
// 导出reducer,在创建store时使用到
export default collapsedSlice.reducer;
import { useSelector, useDispatch } from 'react-redux';
import {changeCollapsed} from '../../redux/features/CollapsedSlice' // 引入actions
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
UserOutlined,
} from "@ant-design/icons";
import React from 'react'
import { Layout, Dropdown, Avatar,Button } from "antd";
import { useNavigate } from 'react-router';
const { Header} = Layout;
export default function TopHeader() {
//根据store.js中设置的reducer名字,从CollapsedSlice空间获取state
const {collapsed} = useSelector(state=>state.CollapsedSlice);
const dispatch = useDispatch();
const handleCollapsed = ()=>{
dispatch(changeCollapsed({value:2}));
}
const { username, role:{roleName} } = JSON.parse(localStorage.getItem("token"));
const navigate = useNavigate();
const items = [
{
key: '1',
label: roleName,
},
{
key: '2',
label: (
<Button type="primary" danger
onClick={() => {
localStorage.removeItem("token");
navigate("/login",{replace:true});
}}
>
退出登录
</Button>
),
},
];
return (
<Header className="site-layout-background" style={{ paddingLeft: "16px" }}>
{/* {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: () => setCollapsed(!collapsed),
})} */}
{
collapsed ? <MenuUnfoldOutlined onClick={handleCollapsed}/>: <MenuFoldOutlined onClick={handleCollapsed}/>
}
<div style={{ display: "inline", float: "right" }}>
<span>
欢迎<span style={{ color: "#1890ff" }}>{username}</span>回来
</span>
<Dropdown menu={{items}}>
<div style={{display:'inline-block',cursor:'pointer'}} onClick={(e) => e.preventDefault()}>
<Avatar size="large" icon={<UserOutlined />} />
</div>
</Dropdown>
</div>
</Header>
)
}
RTK集成了redux-thunk来处理异步事件,所以可以按照之前thunk的写法来写异步请求
AsyncReduxSlice.js
import { createSlice } from "@reduxjs/toolkit";
import axios from 'axios'
const initialState = {
list:[]
}
export const asyncReduxSlice = createSlice({
name:'asyncReduxSlice',
initialState,
reducers:{
changeList(state,{payload}){
state.list = payload.list;
}
}
})
const {changeList} = asyncReduxSlice.actions;
export const getList = payload => {
return dispatch => {
axios.get("/rights?_embed=children").then((res) => {
dispatch(changeList({list:res.data}))
});
}
}
export default asyncReduxSlice.reducer;
Test-AsyncRedux.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux/es/exports'
import {getList} from '../redux/features/AsyncReduxSlice'
export default function TestAsyncRedux() {
const {list} = useSelector(state=>state.AsyncReduxSlice);
const dispatch = useDispatch();
useEffect(()=>{
console.log(111111)
if(list.length === 0){
dispatch(getList())
}else{
alert('列表已被缓存!')
}
},[dispatch,list.length]);
return (
<div>
{
list.map(item=>{
return <li key={item.id}>{item.title}</li>
})
}
</div>
)
}
createAsyncThunk方法可以创建一个异步的action,这个方法被执行的时候会有三个( pending(进行中) fulfilled(成功) rejected(失败))状态。可以监听状态的改变执行不同的操作。以下代码示例中使用到了extraReducers创建额外的action对数据获取的状态信息进行监听。
CreateAsyncThunkSlice.js
import {createSlice,createAsyncThunk} from '@reduxjs/toolkit'
import axios from 'axios'
const getListAPI = ()=>{
return axios.get("/rights?_embed=children").then((res) => res.data);
}
export const loadList = createAsyncThunk(
'CATSlice/loadList',
async ()=>{
const list = await getListAPI();
return list;
}
)
const initialState = {
list:[]
}
export const CATSlice = createSlice({
name:'CATSlice',
initialState,
// 可以额外的触发其他slice中的数据关联改变
extraReducers: builder=>{
builder
.addCase(loadList.pending,state=>{
console.log('进行中');
})
.addCase(loadList.fulfilled,(state,action)=>{
console.log('成功');
const { payload } = action;
// {
// type:'CATSlice/loadList/fulfilled',
// payload://loadList返回值
// }
console.log(action);
state.list = payload;
})
.addCase(loadList.rejected,(state, err)=>{
console.log('失败',err);
})
}
})
export default CATSlice.reducer;
Test-CreateAsyncThunk.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux/es/exports'
import {loadList} from '../redux/features/CreateAsyncThunkSlice'
export default function TestAsyncRedux() {
const {list} = useSelector(state=>{
console.log(state)
return state.CreateAsyncThunkSlice
});
const dispatch = useDispatch();
useEffect(()=>{
console.log(11111111)
if(list.length === 0){
dispatch(loadList())
}else{
alert('列表已被缓存!')
}
},[]);// eslint-disable-line
return (
<div>
{
list.map(item=>{
return <li key={item.id}>{item.title}</li>
})
}
</div>
)
}
当useSelector方法涉及到复杂逻辑运算时,且返回一个对象的时候,每次运行都返回了一个新的引用值,会使组件重新渲染,即使返回的数据内容并没有改变
为了解决这个问题,可以使用Reselect库,它是一个创建记忆化selector的库,只有在输入发生变化时才会重新计算结果,rtk正是集成了这个库,并把它导出为createSelector函数
引用的CreateAsyncThunkSlice.js参照上面
ListFilterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
listFilter:'/home'
}
export const listFilterSlice = createSlice({
// 命名空间,在调用action的时候会默认的设置为action的前缀listFilterSlice/changelistFilter
name:'listFilterSlice',
// 初始值
initialState:initialState,
// 这里的属性会自动的导出为listFilterSlice.actions,在组件中可以直接通过dispatch进行触发
reducers:{
//{ payload }解构出来的payload是dispatch传递的数据对象
changelistFilter(state,action){
// console.log(action)
// {
// "type":"listFilterSlice/changelistFilter",
// "payload":{
// "value":2
// }
// }
// 内置了immutable不可变对象来管理state,不用再自己拷贝数据进行处理
state.listFilter = !state.listFilter;
}
}
})
// 导出actions
export const { changelistFilter } = listFilterSlice.actions;
// 导出reducer,在创建store时使用到
export default listFilterSlice.reducer;
Test-CreateSelector.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux/es/exports'
import {loadList} from '../redux/features/CreateAsyncThunkSlice'
import { createSelector } from '@reduxjs/toolkit';
//当useSelector方法涉及到复杂逻辑运算时,且返回一个对象的时候,每次运行都返回了一个新的引用值,会使组件重新渲染,即使返回的数据内容并没有改变
//为了解决这个问题,可以使用Reselect库,它是一个创建记忆化selector的库,只有在输入发生变化时才会重新计算结果,rtk正是集成了这个库,并把它导出为createSelector函数
const selectList = state=>state.CreateAsyncThunkSlice;
const selectFilter = state=>state.ListFilterSlice;
const filterList = createSelector(selectList,selectFilter,(listState,filterState)=>{
const {list} = listState;
const {listFilter} = filterState;
console.log(listState,filterState)
switch(listFilter){
case 'all':
return list;
case '/home':
return list.filter(item=>item.key === listFilter);
default :
throw new Error('Unknown filter: ' + listFilter);
}
})
export default function TestAsyncRedux() {
const mapList = useSelector(state => filterList(state));
const dispatch = useDispatch();
useEffect(()=>{
console.log(11111111)
if(mapList.length === 0){
dispatch(loadList())
}else{
alert('列表已被缓存!')
}
},[]);// eslint-disable-line
return (
<div>
{
mapList.map(item=>{
return <li key={item.id}>{item.title}</li>
})
}
</div>
)
}
RTK中已经配置了redux-devtools,所以只要浏览器安装调试工具redux-devtools-extension
安装参考redux-devtools-extension