REACT-@reduxjs/toolkit+react-redux+redux-persist状态管理
- 1. 依赖包安装
- 2. 目录结构
- 3. 创建store.js 和 修改Index.js
- 3.1 创建store.js
- 3.2 创建修改Index.js
- 4. createSlice()
- 4.1 action处理
- 4.1.1 创建collapsedSlice
- 4.1.2 使用collapsedSlice
- 4.2 异步action处理
- 4.2.1 使用redux-thunk方式处理异步
- 4.2.1.1 创建asyncReduxSlice
- 4.2.1.2 使用asyncReduxSlice
- 4.2.2 createAsyncThunk()支持thunk
- 4.2.2.1 创建CATSlice
- 4.2.2.1 使用CATSlice
- 5. Selector使用缓存
- 5.1 创建listFilterSlice
- 5.1 使用listFilterSlice
- 6. 调试工具
参考:
Redux最新实践指南之Redux-Toolkit
redux最佳实践redux-toolkit使用指南
react+ts实战之 @reduxjs/toolkit
2015年发布的Redux至今仍是React生态中最常用的状态管理库,2022年4月19日发布的Redux v4.2.0中正式将createStore方法标记为“已弃用”(@deprecated),并推荐用户使用Redux-Toolkit(下面简称为RTK)的configureStore方法。
1. 依赖包安装
npm i @reduxjs/toolkit react-redux redux-persist
2. 目录结构
3. 创建store.js 和 修改Index.js
3.1 创建store.js
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:CollapsedSliceLoadingSlice,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;
3.2 创建修改Index.js
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(// <React.StrictMode><Provider store={store}><PersistGate loading={null} persistor={persistor}><App /></PersistGate></Provider>// </React.StrictMode>
);// 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();
4. createSlice()
使用createSlice方法创建一个slice。每一个slice里面包含了reducer和actions,可以实现模块化的封装。所有的相关操作都独立在一个文件中完成。
4.1 action处理
4.1.1 创建collapsedSlice
CollapsedSlice.js
import { createSlice } from '@reduxjs/toolkit';const initialState = {collapsed:false
}export const collapsedSlice = createSlice({// 命名空间,在调用action的时候会默认的设置为action的前缀collapsedSlice/changeCollapsedname:'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;
4.1.2 使用collapsedSlice
import { useSelector, useDispatch } from 'react-redux';
import {changeCollapsed} from '../../redux/features/CollapsedSlice' // 引入actionsimport {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空间获取stateconst {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>)
}
4.2 异步action处理
4.2.1 使用redux-thunk方式处理异步
RTK集成了redux-thunk来处理异步事件,所以可以按照之前thunk的写法来写异步请求
4.2.1.1 创建asyncReduxSlice
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;
4.2.1.2 使用asyncReduxSlice
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>)
}
4.2.2 createAsyncThunk()支持thunk
createAsyncThunk方法可以创建一个异步的action,这个方法被执行的时候会有三个( pending(进行中) fulfilled(成功) rejected(失败))状态。可以监听状态的改变执行不同的操作。以下代码示例中使用到了extraReducers创建额外的action对数据获取的状态信息进行监听。
4.2.2.1 创建CATSlice
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;
4.2.2.1 使用CATSlice
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-linereturn (<div>{list.map(item=>{return <li key={item.id}>{item.title}</li>})}</div>)
}
5. Selector使用缓存
当useSelector方法涉及到复杂逻辑运算时,且返回一个对象的时候,每次运行都返回了一个新的引用值,会使组件重新渲染,即使返回的数据内容并没有改变
为了解决这个问题,可以使用Reselect库,它是一个创建记忆化selector的库,只有在输入发生变化时才会重新计算结果,rtk正是集成了这个库,并把它导出为createSelector函数
引用的CreateAsyncThunkSlice.js参照上面
5.1 创建listFilterSlice
ListFilterSlice.js
import { createSlice } from '@reduxjs/toolkit';const initialState = {listFilter:'/home'
}export const listFilterSlice = createSlice({// 命名空间,在调用action的时候会默认的设置为action的前缀listFilterSlice/changelistFiltername:'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;
5.1 使用listFilterSlice
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-linereturn (<div>{mapList.map(item=>{return <li key={item.id}>{item.title}</li>})}</div>)
}
6. 调试工具
RTK中已经配置了redux-devtools,所以只要浏览器安装调试工具redux-devtools-extension
安装参考redux-devtools-extension