react+ts中rtk的基本使用

本文为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中的方法、异步请求的方法以及所有涉及bannerrecommend数据的地方就可以去消费。

@/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;
相关推荐
高山我梦口香糖1 小时前
[react 3种方法] 获取ant组件ref用ts如何定义?
typescript·react
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
prall3 小时前
实战小技巧:下划线转驼峰篇
前端·typescript
飞翔的渴望5 小时前
antd3升级antd5总结
前端·react.js·ant design
╰つ゛木槿9 小时前
深入了解 React:从入门到高级应用
前端·react.js·前端框架
用户305875848912512 小时前
Connected-react-router核心思路实现
react.js
哑巴语天雨1 天前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情1 天前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
码农老起1 天前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
前端没钱1 天前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js