React TypeScript 应用中便捷使用Redux Toolkit方法详解
作者:BokFang 发布时间:2023-08-11 09:48:21
前言
本文介绍的主要内容是 Redux-Toolkit 在 React + TypeScript 大型应用中的实践,主要解决的问题是使用 createSlice 的前提下消费 redux 状态仍旧有点繁琐的问题。
阅读本文需要的前置知识:了解 React 、Redux-Toolkit 、TypeScript 的使用。
关于 Redux-Toolkit 提供的各种函数的使用,大家可以去官网 redux-toolkit.js.org/ 学习。
背景
前阵子接到一个任务:在使用 redux 作为状态管理工具的前提下,优化一下消费 redux 的步骤,并制定一套使用规范,让大家在开发这个项目消费 redux 状态时能按照规范来。
说到简化消费 redux 步骤,我第一时间想到的就是 redux 官方推荐的 Redux-Toolkit,于是我就去学习了一下 Redux-Toolkit。
了解完官网和网上各种文章后,我知道了 Redux-Toolkit 在项目中的使用,但是仍然有一个疑问:使用了 createSlice 后,仍然需要在项目的组件中使用 useDispatch 来更新状态,还是有点麻烦。对于组件使用者来说,有没有更方便的方式消费 redux 状态?
在网上逛来逛去,没找到有人发相关的文章,所以我准备自己动手试试看。
Redux-Toolkit 常规使用
我们先来看看目前使用 Redux-Toolkit 消费 redux 状态的方式。举个例子,假设我们现在的业务和蛋糕有关,有两个状态存在 redux,分别是 nameOfCake 和 numOfCakes,我们使用 createSlice 来创建 reducer 和 actions:
// 文件位置: app/src/store/reducers/cake.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type InitialState = {
numOfCakes: number;
nameOfCake: string;
};
const initialState: InitialState = {
numOfCakes: 20,
nameOfCake: 'great cake',
};
const cakeSlice = createSlice({
name: 'cake',
initialState,
reducers: {
updateCakeNum: (state, action: PayloadAction<number>) => {
state.numOfCakes = action.payload;
},
updateCakeName: (state, action: PayloadAction<string>) => {
state.nameOfCake = action.payload;
},
},
});
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;
接着我们在组件中消费:
// 文件位置: app/src/pages/Cake/components/CakeView/index.tsx
import { RootState } from '@/src/store';
import { useSelector, useDispatch } from 'react-redux';
import {
useRecordReduxFunction,
updateCakeNum,
updateCakeName,
} from '@/src/store/reducers/cake';
export const CakeView = () => {
const numOfCakes = useSelector((state: RootState) => state.cake.numOfCakes);
const nameOfCake = useSelector((state: RootState) => state.cake.nameOfCake);
const dispatch = useDispatch();
const updateNum = () => {
dispatch(updateCakeNum(100));
};
const updateName = () => {
dispatch(updateCakeName('best cake'));
};
return (
<div>
<h3>Number of cakes - {numOfCakes}</h3>
<h3>Name of cakes - {nameOfCake}</h3>
<button onClick={updateName}>change cake's name</button>
<button onClick={updateNum}>change cake's number</button>
</div>
);
};
现状的繁琐点:
每次使用 useSelector 来获取 redux 中状态的时候,都需要给 state 加一个 ts 类型 RootState
每次修改 redux 状态时,都需要引入 useDispatch 和一个 updateData,再将调用 updateData 返回的action 给 dispatch
我们的目标就是优化现状的这两个繁琐点。
优化方案
优化 useDispatch 和 useSelector
对于繁琐点1,我们可以对 useDispatch 和 useSelector 进行简单的封装,增加 ts 类型校验。代码如下:
// 文件位置: app/src/hooks/useReduxHook.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
其中 RootState 和 AppDispatch 在 store 中导出:
import { configureStore } from '@reduxjs/toolkit';
import cakeReducer from './reducers/cake';
const store = configureStore({
reducer: {
cake: cakeReducer,
}
})
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
通过简单的封装,我们在业务层就用 useAppSelector 和 useAppDispatch 代替 useSelector 和 useDispatch:
import { useAppSelector, useAppDispatch } from '@src/hooks/useReduxHook';
// ...
export const CakeView = () => {
const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
const nameOfCake = useAppSelector((state) => state.cake.nameOfCake);
const dispatch = useAppDispatch();
// ...
return (
// ...
);
};
可以看到这样就有 ts 类型提示或者校验了:
优化修改 redux 状态的步骤
我们现在想要在业务组件中修改 redux 状态,我们就需要引入 useDispatch 和通过 createSlice 生成的 updateData 函数,再调用 dispatch(updateData(data))
来更新状态。
能不能优化成,业务组件只需要调用一个 updateData 函数,就可以更新 redux 状态呢?
最简单的方式就是将 dispatch(updateData(data))
给抽出去:
const useUpdateCakeName = () => {
const dispatch = useAppDispatch();
return (payload: InitialState['nameOfCake']) => {
dispatch(updateCakeName(payload));
};
};
但是这对于开发者来讲,换汤不换药,还是需要写一个 hook 来 dispatch action。
那我们就来写一个能自动生成 「用于dispatch action 的 updateData 函数」的 hook 吧。先来个 js 版:
const useCakeReduxFunction = (action) => {
const dispatch = useAppDispatch();
return (payload) => {
dispatch(cakeSlice.actions[action](payload));
};
};
这个倒是好用了一些,业务组件使用起来是这样的:
import { useCakeReduxFunction } from '@/src/store/reducers/cake';
// ...
export const CakeView = () => {
const updateNum = useCakeReduxFunction('updateCakeNum');
const updateName = useCakeReduxFunction('updateCakeName');
// ...
return (
// ...
);
};
但是还有个问题,这样的话每个 reducer 里都要写一个 useDataReduxFunction,还是不够便捷。我们就自然而然的想到写一个 useCreateReduxFunction 来简化开发流程。useCreateReduxFunction 的代码如下;
type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useCreateReduxFunction = <S extends Slice>(slice: S) => {
return <T extends keyof S['actions']>(name: T) => {
const dispatch = useAppDispatch();
const actionCreator = (slice.actions as S['actions'])[name];
return (payload: GetArrFirst<Parameters<typeof actionCreator>>) => {
dispatch(actionCreator(payload));
};
};
};
这样就好办了,我们在 reducer 中这样使用:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { useCreateReduxFunction } from '../../app/hooks';
type InitialState = {
numOfCakes: number;
cakeName: string;
};
const initialState: InitialState = {
numOfCakes: 20,
cakeName: 'great cake',
};
const cakeSlice = createSlice({
name: 'cake',
initialState,
reducers: {
updateCakeNum: (state, action: PayloadAction<number>) => {
state.numOfCakes = action.payload;
},
updateCakeName: (state, action: PayloadAction<string>) => {
state.cakeName = action.payload;
},
},
});
export const useCakeReduxFunction = useCreateReduxFunction(cakeSlice);
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;
在业务方这么使用:
import { useAppSelector } from '@src/hooks/useReduxHook';
import { useCakeReduxFunction } from '@src/store/reducers/cake';
export const CakeView = () => {
const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
const nameOfCake = useAppSelector((state) => state.cake.cakeName);
const updateNum = useCakeReduxFunction('updateCakeNum');
const updateName = useCakeReduxFunction('updateCakeName');
return (
<div>
<h3>Number of cakes - {numOfCakes}</h3>
<h3>Name of cakes - {nameOfCake}</h3>
<button onClick={() => {updateName('best cake')}}>change cake's name</button>
<button onClick={() => {updateNum(100)}}>change cake's number</button>
</div>
);
};
这样就达成我们的目标了。
来源:https://juejin.cn/post/7168260892914614309


猜你喜欢
- 在学习asp过程中相信很多初学者对Sub与Function的用法有些疑惑,好像它们没什么区别都可以使用。呵呵,看了本文的介绍您就可以了解了S
- 不久前因业务需要,我在自己的笔记本中安装了搜霸。当时一个做平面的朋友过来和我做一些设计交流,我在笔记本前准备输入一个网址,他靠近我的电脑,大
- 本文实例为大家分享了文字无缝滚动效果,供大家参考,具体内容如下html<dl id="marquee" class
- 一维列表的初始化:初始一个长度为5的列表方式1:a = [0]*5# [0, 0, 0, 0, 0]方式2:a = [0 for _ in
- 本文实例讲述了Flask框架 CSRF 保护实现方法。分享给大家供大家参考,具体如下:Flask CSRF 保护为什么需要 CSRF?具体操
- 使用 sorted() 函数使用 sorted() 函数对字典进行排序,将其转换为元组列表,再按照指定的键或者值进行排序。按照键排序的示例代
- Scrapy是一个用Python实现的为了爬取网站数据、提取数据的应用框架。我们对于爬取到的数据存储到本地或数据库是经常要用到的操作。主要讲
- 输入汉字提示拼音,试试下面这个函数,不知是不是你要的那个:查询汉字便宜到词典网<%function getpychar(ch
- 1、什么是mixin这不是Vue的专属的,可以说是一种思想,在很多开发框架中都实现了Mixin。官方解释:mixin提供了一种非常灵活的方式
- 前言众所周知,Python中没有所谓的main函数,但是网上经常有文章提到“ Python的main函数&rdq
- list.asp<%@LANGUAGE="VBSCRIPT" CODEPAGE="936&qu
- @property作用:python的@property是python的一种装饰器,是用来修饰方法的。我们可以使用@property装饰器来
- python3.6在运行tkinter时要选择 run as Python unit-test,否则报错 ModuleNotFoundErr
- 操作步骤导入框架,import unitest测试类必须继承类:.class 类名(unittest.TestCase):在类中所有定义te
- 这个收藏本站、设为首页代码相信每个网站都会用到,这么常用的代码,网络上流行的一般是很多年前的代码版本,只有兼容IE,对其它浏览器没有考虑,下
- 本文为大家分享了mysql 5.7.16 免安装版本教程,供大家参考,具体内容如下MySQL: 5.7.16 程序目
- 为什么要使用php缓存技术?理由很简单:提高效率。在程序开发中,获取信息的方式主要是查询数据库,除此以外,也可能是通过Web Service
- 一、项目展示这是一款简单实用的小时钟工具分为工作和休息两种状态用户可以设置相应的时间所有的时钟记录都会被保存下来二、首页首页由计时器、任务输
- 本文主要跟大家介绍了Golang巧用defer进行错误处理的相关内容,分享出来供大家参考学习,下面来看看详细的介绍:问题引入毫无疑问,错误处
- 1、登录mysqlmysql -uroot -p2、先查询都有哪些用户select host,user from mysql.user;红色