本文为react+ts项目中使用rtk的经验贴,ts类型方面踩了一些坑,比较浪费时间。这里记录一下方便遇到同样场景的朋友以及自己后续使用时快速cv
根store定义
根store的配置,src/store/index.ts
:
ts
// recommendReducer即为recommend路由组件(一个需要建立子store的组件)的reducer
import recommendReducer from '../application/Recommend/store';
import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
const store = configureStore({
reducer: {
recommend: recommendReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// 使用 useAppDispatch/useAppSelector 代替 useDispatch/useSelector 提供ts类型支持
// 消费组件中使用useAppDispatch/useAppSelector与useDispatch/useSelector在运行时完全没有区别
// 如下,useAppDispatch/useAppSelector只是对useDispatch/useSelector进行了单纯的类型标注而已
type DispatchFunc = () => AppDispatch;
export const useAppDispatch: DispatchFunc = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export default store;
根组件中通过context传递根store,App.tsx
:
tsx
import { Provider } from 'react-redux';
import store from './store';
// xxx
function App() {
return (
<Provider store={store}>
{/*xxx*/}
</Provider>
);
}
export default App;
子store定义
目录结构参考
建立子store的组件目录结构:
lua
application/ --路由组件文件夹
└── recommend/
├── index.tsx
└── store/ --recommend组件所属子store
└── index.ts
子store定义逻辑
先忽略类型定义,理顺js逻辑,@/application/recommend/store/index.ts
:
ts
import {
TdefaultState,
TBannerList,
TRecommendList,
} from '../../../types/recommend'; // 类型相关,暂时忽略
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import {
getBannerRequest,
getRecommendListRequest,
} from '../../../api/request'; // 获取异步数据的请求api
// 子store的初始数据
const defaultState: TdefaultState = {
bannerList: [],
recommendList: [],
};
// 涉及异步请求的action函数用createAsyncThunk来创建
// 第一个参数为任意字符串,标识作用,可能在devtool中有所帮助
// 第二个参数即为异步函数,函数返回值将作为下面创建的子store.extraReducers的钩子函数中的action.payload(然后用来修改store中的state)
export const getBannerList = createAsyncThunk('changeBannerList', async () => {
try {
const data = await getBannerRequest();
return data.banners;
} catch (e) {
console.log(e, '轮播图数据传输错误');
}
});
export const getRecommendList = createAsyncThunk(
'changeRecommendList',
async () => {
try {
const data = await getRecommendListRequest();
return data.result;
} catch (e) {
console.log(e, '推荐歌单数据传输错误');
}
},
);
// 子store用createSlice来创建
const recommendSlice = createSlice({
name: 'recommend', // name为标识作用的字符串
initialState: defaultState, // initialState即为状态初始值
// reducers里即为修改state的同步操作,这里的state即为这个小store本身的state,就是initialState
// reducers里的函数最终的目的自然是暴露给外面去调用的,所以最后需要export
// 上面如getBannerList这种涉及异步操作的修改state的函数本质也是为了对外暴露然后被调用的,所以直接export即可
reducers: {
changeBannerList(state, action: PayloadAction<TBannerList>) {
state.bannerList = action.payload;
},
changeRecommendList(state, action: PayloadAction<TRecommendList>) {
state.recommendList = action.payload;
},
},
// 上面的getBannerList/getRecommendList与此store是无关联的,extraReducers就是将异步函数与子store作联系
// 联系方式:[<异步函数名>.<异步操作所返回的promise状态>.type]: <对应状态执行的函数回调>
extraReducers: {
[getRecommendList.fulfilled.type]: (state, action) => { // state即为子store的state(initialState);action.payload即为异步函数的返回值
state.recommendList = action.payload;
},
[getBannerList.fulfilled.type]: (state, action) => {
state.bannerList = action.payload;
},
},
});
export const { changeBannerList, changeRecommendList } = recommendSlice.actions; // 从recommendSlice.actions中暴露同步action方法(reducers中的方法)
export default recommendSlice.reducer; // 默认导出recommendSlice.reducer,这个即为根store所需的子store
其实类型相关就与redux关系不大了,上面唯一与redux相关的类型就是给reducers里的同步方法增加了一个PayloadAction
类型,其泛型对应action.payload的类型。
项目类型定义实践
简单记录一下类型定义:
recommend
模块所需的数据对应的类型定义存放在@/types/recommend.ts
中,@/types/recommend.ts
:
ts
// banner对象的类型定义
export interface IBannerItemBase {
imageUrl: string; // 当下业务所必须的属性
}
export interface IBannerItem extends IBannerItemBase {} // 留作未来拓展
// recommend数据对象与banner类似
export interface IRecommendItemBase {
id: number;
picUrl: string;
playCount: number;
name: string;
}
export interface IRecommendItem extends IRecommendItemBase {}
// (借助上面的对象类型)实现banner数组的类型
export type TBannerList = Array<IBannerItem> | [];
export type TRecommendList = Array<IRecommendItem> | []; // recommend数组类型
// (借助上面的数组类型)最终实现store的state的完整类型
export type TdefaultState = {
bannerList: TBannerList;
recommendList: TRecommendList;
};
类型的定义都是由局部到整体,先实现局部小类型,慢慢组装成复杂变量的完整的类型定义
有了上面关于数据类型的定义,store中的方法、异步请求的方法以及所有涉及banner
与recommend
数据的地方就可以去消费。
如@/types/request.ts
中去定义异步请求的响应体结构,@/types/request.ts
:
ts
// 一个异步请求接口对应一个响应体结构(接口设计时字段及结构可能不统一),一个响应体结构对应一个interface接口定义
export interface IBannerRequestStruct<TData = never> {
code: number;
banners: TData;
}
export interface IRecommendRequestStruct<TData = never> {
code: number;
result: TData;
}
在@/api/request.ts
中去定义真正的异步请求方法同时消费上面定义的类型:
ts
import { axiosInstance } from './config'; // 导入axios实例
import {
IRecommendRequestStruct,
IBannerRequestStruct,
} from '../types/request';
import { TBannerList, TRecommendList } from '../types/recommend';
// 获取banner数据
// Promise的泛型即为promise实例在fullfilled状态时的value,即为await解构得到的值,这里即为IBannerRequestStruct<TBannerList>,也就是响应体的类型
export const getBannerRequest: () => Promise<
IBannerRequestStruct<TBannerList>
> = () => {
return axiosInstance.get('/banner');
};
// 获取推荐列表
export const getRecommendListRequest: () => Promise<
IRecommendRequestStruct<TRecommendList>
> = () => {
return axiosInstance.get('/personalized');
};
组件中消费
经过上面的配置,整个store,包括根store以及子store就已经定义好了,最后就是如何在组件中进行store中状态的消费,@/application/Recommend/index.tsx
:
tsx
import { useAppDispatch, useAppSelector } from '../../store'; // ts中需要使用useAppDispatch、useAppSelector
import { RootState } from '../../store'; // 调用useAppSelector时给根state标注类型使用
import { getBannerList, getRecommendList } from './store';
function Recommend(): JSX.Element {
const dispatch = useAppDispatch(); // 使用useAppDispatch获取dispatch函数
const bannerList = useAppSelector(
(state: RootState) => state.recommend.bannerList,
);
const recommendList = useAppSelector(
(state: RootState) => state.recommend.recommendList,
);
useEffect(() => {
dispatch(getBannerList());
dispatch(getRecommendList());
}, []);
return (
<div>
<Slider bannerList={bannerList}></Slider>
<RecommendList recommendList={recommendList} />
</div>
);
}
export default Recommend;