当前位置:   article > 正文

【REACT-@reduxjs/toolkit+react-redux+redux-persist状态管理】

reduxjs/toolkit


参考:
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
  • 1

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: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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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/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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4.1.2 使用collapsedSlice


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>
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
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>
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

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-line

  return (
    <div>
      {
        list.map(item=>{
          return <li key={item.id}>{item.title}</li>
        })
      }
    </div>
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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/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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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-line

  return (
    <div>
      {
        mapList.map(item=>{
          return <li key={item.id}>{item.title}</li>
        })
      }
    </div>
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

6. 调试工具

RTK中已经配置了redux-devtools,所以只要浏览器安装调试工具redux-devtools-extension
安装参考redux-devtools-extension

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/510818
推荐阅读
相关标签
  

闽ICP备14008679号