前言:
在react中处理公共数据有很多种方法,我们这里来说一说用 react+redux+toolkit 来实现公共数据的处理。
功能对比 redux与Vuex
功能 | Vuex | Redux Toolkit |
---|---|---|
状态定义 | state |
initialState |
同步修改 | mutations |
reducers |
异步操作 | actions |
createAsyncThunk + extraReducers |
模块化 | modules |
多个 slices + combineReducers |
派生状态 | getters |
自定义 selector hooks |
组件绑定 | mapState , mapActions |
useSelector , useDispatch |
安装
react-redux是状态管理工具,@reduxjs/toolkit是redux的工具包(官方大力支持,代码量更小,使用更方便)
npm i react-redux @reduxjs/toolkit
redux-persist 是数据持久化工具,可以存到本地
redux-persist-transform-encrypt 是数据加密的方法
npm i redux-persist redux-persist-transform-encrypt
使用:
1、main.jsx中注册下
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
// 导入合并好的stores
import stores from './stores'
// 导入reduxProvider组件传递store
import { Provider } from 'react-redux'
createRoot(document.getElementById('root')).render(
//使用redux的元素包起来,也可以放app.jsx中
<Provider store={stores}>
<App />
</Provider>
)
2、stores中,注册多个文件
类似vuex中得modules
index.js,可以引入多个,然后通过reducer来合并
import { configureStore } from "@reduxjs/toolkit";
import user from './user'
import shop from './shop'
export default configureStore({
reducer:{
user,
shop
}
})
3、user.js
initialState 里面定义的变量跟vuex里面定义变量state一样,跟vue的data定义变量一样
reducers里面定义的方法函数跟vuex里面mutations一样
import { createSlice } from '@reduxjs/toolkit'
// 创建userSlice切片,存储关于user的数据和修改user数据的方法action
const userSlice = createSlice({
name: 'user-slice',// 每个切片的名字
initialState: ({ // 定义数据
user: {
name: '张三',
age: 18,
gender: '男'
}
}),
reducers: { // 定义方法集合
setName: (state, action) => {
state.user.name = action.payload
},
setAge: (state, action) => {
state.user.age = action.payload
},
setGender: (state, action) => {
state.user.gender = action.payload
}
}
})
// 导出修改数据的方法,在组件中使用
export const { setName, setAge, setGender } = userSlice.actions
export default userSlice.reducer // 导出切片
4、shop.js
跟上面user一样的写法, 加了个异步请求接口的方法,这里可以监听他的不同状态
等同于vuex的actions异步方法
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios';
// 异步 action(类似 Vuex 的 actions)
export const fetchUser = createAsyncThunk(
'user/fetchUser', // action 类型前缀
async (userId) => {
const response = await axios.get(`/api/users/${userId}`);
return response.data; // 返回值作为 action.payload
}
);
const shopSlice = createSlice({
name: 'shop-slice',
initialState: ({
// 接口拿到的数据与loading状态等
data: null,
loading: false,
error: null
shopList: [
{
name: 'xiaomi 15',
price: '3999',
id: 1
}
]
}),
reducers: ({
// 同步-修改数组变量
setName: (state, action) => {
state.shopList[0].name = action.payload
}
}),
extraReducers: (builder) => { // 处理异步 action 的不同阶段
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
})
.addCase(fetchUser.rejected, (state, action) => {
state.error = action.error.message;
state.loading = false;
});
},
})
export const { setName} = shopSlice.actions
export default shopSlice.reducer
5、到这一步,可以直接在界面用了,比如demo.jsx中
dispatch(setAge() ) 这就是调用同步方法 的使用,类似调用vuex里面的mutation方法
dispatch(fetchUser(12333)) 调用异步方法的使用,类似调用vuex里面actions里面方法
import React from 'react'
import { useSelector, useDispatch } from 'react-redux' //固定使用
import { setAge } from '@/stores/user' //对应export 的对象 里面的函数方法名
export default function Child() {
const user = useSelector(state => state.user.user) //直接拿到定义的变量
const dispatch = useDispatch()
return (
<div>
<h1>用户:{JSON.stringify(user)}</h1>
<button onClick={()=>dispatch(setAge(user.age+1))}>年龄加一</button>
</div>
)
}
//异步调用接口
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchUser } from '@/store/slices/user';
import { useUser } from '@/store/hooks';
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading, error } = useUser(); // 类似 mapState
useEffect(() => {
dispatch(fetchUser(userId)); // 类似 Vuex 的 actions
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>Username: {data?.name}</div>;
}
6、正常项目中不会用这么简单方法,还可以定义类型,以及封装hook
比如类型的type
export interface RootState{
key: string
path: string
title: string
}
然后使用类型,以及发送上面定义变量的hook
import { useSelector } from 'react-redux';
import { RootState } from './type';
// 封装自定义 selector hooks,统一在这个文件,界面用的时候统一从这个文件取,不用单独引入
export const useUser = () => useSelector((state: RootState) => state.user);
export const useCounter = () => useSelector((state: RootState) => state.counter);
// 计算属性(类似 Vuex 的 getters)
export const useDoubleCount = () =>
useSelector((state: RootState) => state.counter.value * 2);
7、第6步里面的类型定义是自定义用interface定义类型,也可以这样搞
在store/index里面
// 导出类型化的 hooks
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
然后再hook里面,接收他的定义好的类型,不用我们自定义去写
import type { RootState } from './index';
8、如果使用了数据持久化方法redux-persist
只需要在store/index文件里
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
//import sessionStorage from 'redux-persist/lib/storage/session';
import authReducer from './slices/authSlice';//分片,其它文件
const persistConfig = {
key: 'auth', //存储的key
storage, //默认是localStorage
};
const persistedReducer = persistReducer(persistConfig, authReducer);//存起来
export const store = configureStore({
reducer: {
auth: persistedReducer,
},
});
export const persistor = persistStore(store);//最后发送的时候,把缓存也带上
9、如果使用了加密encrypt插件
解密是自动完成的,注意一点,如果旧数据加密存在本地,新数据修改了key,那需要删除缓存内容
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import encryptTransform from 'redux-persist-transform-encrypt';
const persistConfig = {
key: 'root',
storage,
transforms: [
encryptTransform({
secretKey: 'my-super-secret-key',// 加密key
onError: (err) => console.error('加密失败:', err),
}),
],
};