React TypeScript 应用中便捷使用Redux Toolkit方法详解

目录
  • 前言
  • 背景
  • Redux-Toolkit 常规使用
  • 优化方案
    • 优化 useDispatch 和 useSelector
    • 优化修改 redux 状态的步骤
  • 总结

前言

本文介绍的主要内容是 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>
  );
};

这样就达成我们的目标了。

总结

核心代码如下:

type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
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));
    };
  };
};

经过上面的优化方案,我们封装了 useAppSelector 、 useAppDispatch 和 useCreateReduxFunction三个 hook,解决了目前使用 Redux-Toolkit 还会存在的一些繁琐点。

以上就是React TypeScript 应用中便捷使用Redux Toolkit方法详解的详细内容,更多关于React TypeScript使用Redux Toolkit的资料请关注我们其它相关文章!

(0)

相关推荐

  • TypeScript 背后的结构化类型系统原理详解

    目录 前言 什么是结构化类型系统? 什么是标称类型系统? 结构化类型系统等价于鸭子类型系统吗? 如何在 TypeScript 中模拟标称类型系统? 交叉类型实现 类实现 总结 前言 你能说清楚类型.类型系统.类型检查这三个的区别吗?在理解TypeScript的结构化类型系统之前,我们首先要搞清楚这三个概念和它们之间的关系 类型:即对变量的访问限制与赋值限制.如 TypeScript 中的原始类型.对象类型.函数类型和字面量类型等类型,当一个变量类型确定后,你不能访问这个类型中不存在的属性或方法,

  • 详解如何发布TypeScript编写的npm包

    目录 前言 项目 初始化项目 构建库 添加测试 发布 测试一下 总结 前言 在这篇文章中,我们将使用TypeScript和Jest从头开始构建和发布一个NPM包. 我们将初始化一个项目,设置TypeScript,用Jest编写测试,并将其发布到NPM. 项目 我们的库称为digx.它允许从嵌套对象中根据路径找出值,类似于lodash中的get函数. 比如说: const source = { my: { nested: [1, 2, 3] } } digx(source, "my.nested[

  • ESLint规范TypeScript代码使用方法

    目录 前导 安装依赖 Rules fix 聊一聊原理 parser plugins extends 总结 前导 ESLint 是一种 JavaScript linter,可以用来规范 JavaScript 或 TypeScript 代码,本文教你怎么在项目中快速把 ESLint 安排上. 怎么写出优雅的代码是一个老生常谈的话题,这其中包含了太多内容可聊,但搞一套标准规范绝对是万里长征第一步.ESLint 致力于如何把规范落地到你的项目中,让你的代码清清爽爽. ESLint 作为一种 JavaSc

  • 教你30秒发布一个TypeScript包到NPM的方法步骤

    文章读译自The 30 second guide to publishing a typescript package to npm,部分内容有修改哈. 这篇文章要求你有一定的 JS .TS 和 NPM 的知识,如果你写过普通的 NPM 包就更好啦~如果没有的话网上也很多教程的,都很简单~ 发布过 npm 包的同学都知道,初始化一个 npm 项目,直接用 npm init -y 就可以了,那如果要用 ts 呢,直接 tsc --init 即可.这两个操作会生成 package.json 和 ts

  • 使用typescript开发angular模块并发布npm包

    本文介绍了使用typescript开发angular模块并发布npm包,分享给大家,具体如下: 创建模块 初始化package.json文件 执行命名 npm init -y 会自动生成package.json文件如下,name默认为文件夹名称 { "name": "MZC-Ng-Api", "version": "1.0.0", "description": "", "mai

  • 从零使用TypeScript开发项目打包发布到npm

    前言 typescript作为未来前端开发的主流框架,在前端开发的过程中也会越来越主要,相信这篇文章会对你有很大的帮助! 开发环境搭建 创建ming-npm-package文件夹 我在桌面上创建了一个ming-npm-package的文件夹,然后在编辑器里面打开 初始化项目 npm init 通过npm init 初始化项目来创建用户package.json文件 也可以npm init -y 这个是使用的默认的配置,我个人使用的是npm init 设置配置项 package name: (min

  • React TypeScript 应用中便捷使用Redux Toolkit方法详解

    目录 前言 背景 Redux-Toolkit 常规使用 优化方案 优化 useDispatch 和 useSelector 优化修改 redux 状态的步骤 总结 前言 本文介绍的主要内容是 Redux-Toolkit 在 React + TypeScript 大型应用中的实践,主要解决的问题是使用 createSlice 的前提下消费 redux 状态仍旧有点繁琐的问题. 阅读本文需要的前置知识:了解 React .Redux-Toolkit .TypeScript 的使用. 关于 Redux

  • TypeScript合并两个排序链表的方法详解

    目录 前言 思路分析 实现代码 测试用例 示例代码 前言 给定两个递增排序的链表,如何将这两个链表合并?合并后的链表依然按照递增排序.本文就跟大家分享一种解决方案,欢迎各位感兴趣的开发者阅读本文. 思路分析 经过前面的学习,我们知道了有关链表的操作可以用指针来完成.同样的,这个问题也可以用双指针的思路来实现: p1指针指向链表1的头节点 p2指针指向链表2的头节点 声明一个变量存储合并后的链表,比对两个指针指向的节点值大小: 如果p1指针指向的节点值比p2指向的值小,合并后的链表节点就取p1节点

  • JavaScript 中有关数组对象的方法(详解)

    JS 处理数组多种方法 js 中的数据类型分为两大类:原始类型和对象类型. 原始类型包括:数值.字符串.布尔值.null.undefined 对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象).数组(键值的有序集合). 数组元素的添加 arrayObj.push([item1 [item2 [. . . [itemN ]]]]); 将一个或多个新元素添加到数组结尾,并返回数组新长度 arrayObj.unshift([item1 [item2 [. . .

  • Mongodb中MapReduce实现数据聚合方法详解

    Mongodb是针对大数据量环境下诞生的用于保存大数据量的非关系型数据库,针对大量的数据,如何进行统计操作至关重要,那么如何从Mongodb中统计一些数据呢? 在Mongodb中,给我们提供了三种用于数据聚合的方式: (1)简单的用户聚合函数: (2)使用aggregate进行统计: (3)使用mapReduce进行统计: 今天我们首先来讲讲mapReduce是如何统计,在后续的文章中,将另起文章进行相关说明. MapReduce是啥呢?以我的理解,其实就是对集合中的各个满足条件的文档进行预处理

  • Javascript中的迭代、归并方法详解

    迭代方法 在Javascript中迭代方法个人觉得尤为重要,在很多时候都会有实际上的需求,javascript提供了5个迭代方法来供我们操作,它们分别为: every() 对数组中的每一个项运用给定的函数,如果每项都返回true,那么就会返回true filter() 对数组中的每一个项运用给定的函数,把返回true的项组成一个新数组并返回 forEach() 对数组中的每一项运用给定的函数,但是没有任何的返回值 map() 对数组中的每一个项运用给定的函数并返回每次函数调用的结果组成新的数组

  • js基础之DOM中元素对象的属性方法详解

    在 HTML DOM (文档对象模型)中,每个部分都是节点. 节点是DOM结构中最基本的组成单元,每一个HTML标签都是DOM结构的节点. 文档是一个    文档节点 . 所有的HTML元素都是    元素节点 所有 HTML 属性都是    属性节点 文本插入到 HTML 元素是    文本节点 注释是    注释节点. 最基本的节点类型是Node类型,其他所有类型都继承自Node,DOM操作往往是js中开销最大的部分,因而NodeList导致的问题最多.要注意:NodeList是'动态的',

  • JavaScript中Number对象的toFixed() 方法详解

    定义和用法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字. 语法 NumberObject.toFixed(num) 参数 描述 num 必需.规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围.如果省略了该参数,将用 0 代替. 返回值 返回 NumberObject 的字符串表示,不采用指数计数法,小数点后有固定的 num 位数字.如果必要,该数字会被舍入,也可以用 0 补足,以便它达到指定的长度.如果 num 大于 l

  • JavaScript中关键字 in 的使用方法详解

    for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为"枚举". 对于数组 ,迭代出来的是数组元素 但不推荐,因为不能保证顺序,而且如果在Array的原型上添加了属性,这个属性也会被遍历出来,所以 最好数组使用正常的for循环,对象使用for-in循环 对于对象 ,迭代出来的是对象的属性: var obj = { "key1":"value1", "key2":"value2", &q

  • Struts中使用validate()输入校验方法详解

    1.在ActionSupport中有一个validate()方法,这个方法是验证方法,它会在execute()方法执行之前执行,所以能够起到很好的验证的作用. @Override //重写Action中的validate()方法 public void validate() { if(null==this.username||this.username.length()<4||this.username.length()>6){ this.addActionError("userna

  • struts2中使用注解配置Action方法详解

    使用注解来配置Action可以实现零配置,零配置将从基于纯XML的配置转化为基于注解的配置.使用注解,可以在大多数情况下避免使用struts.xml文件来进行配置. struts2框架提供了四个与Action相关的注解类型,分别为ParentPackage.Namespace.Result和Action. ParentPackage:ParentPackage注解用于指定Action所在的包要继承的父包.该注解只有一个value参数.用于指定要继承的父包. 示例: 使用ParentPackage

随机推荐