基于 Redux + TypeScript 实现强类型检查和对 Json 的数据清理
基于 Redux + TypeScript 实现强类型检查和对 Json 的数据清理
突然像是打通了任督二脉一样就用了 generics 搞定了之前一直用 any 实现的类型……
关于 Redux 的部分,这里不多赘述,基本的实现都在这里:Redux Toolkit 调用 API 的四种方式 和 async thunk 解决 API 调用的依赖问题。
之前的实现方法是这个:TS 使用自动提示生成对象中的键,不过实现了一下还是稍微麻烦了一些,而且 CRUD 中的 Create 操作比较难(因为缺乏默认值),之后还是换了一种写法。
虽然这里是用的是 react redux,不过因为不涉及到渲染部分,以及 redux 也可以在非 react 项目中使用,所以还是放到 TS 分类中了(x
下面就根据自己的经验和目前写的项目讲一下我自己琢磨出来的实现。
对象的类型定义
这种主要是通过 type or interface 去实现,具体没什么差别啦,不过对于我们来说,数据类型是固定的,没必要修改对应的数据类型,使用 type 就好了。具体用 type 还是 interface,还是具体需求具体分析。
另外 type 的优势就是少打一些字……?
具体实现如下:
export type IPost = {body: string;// 因为网上数据和项目数据格式不一致,所以这里暂时注释掉// id: number;title: string;userId: number;
};// 这个跟 redux 存储状态相关
// export interface IPostModel extends ISimpleDataFormat<IPost> {}
export type IRdmMarketModel = ISimpleDataFormat<IRdmMarket>;
随后定义一个默认值:
export const defaultPost: IPost = {body: '',id: 0,title: '',userId: 0,
};
这样就完成了最初的设定。
Redux 的类型配置
Redux 的配置就比较复杂了,这一个也是比较项目相关的部分,下面也会一步步地进行拆解进行实现。
API 传来的数据类型
initialState 还是挺重要的,因为我们的 API 有十几二十多个,每一个的存储类型都是一致的,因此就不可能说将同样的东西 cv 十几二十遍。最终我设计的 Redux 存储格式为:
我们的业务是,后端绝对会返回一个 data 的数组,其中的数据类型大致为:
{"data": [{"type": "","id": 0,"attributes": {},// 并不一定存在"relationships": {}}],// include 也是外链关系,为relationship所包含的entity,数据类型与data一致"includes": {}
}
其中 type 为当前 entity 的名称,attributes 为当前 entity 所包含的属性,relationship 为可能存在的外链,includes 通过 query parameters 调用,为 relationship 中,外链对象的具体数据。
在具体存储的时候,我想把 id 放到 attributes 中,这样转化为数组给 UI 组件时会方便一些。于是,我定义了 ISimpleDataFormat 的类型:
export type IRelationship = {data: {type: string;id: number;};
};// 其实这个用 { [key: string]: IRelationship } 的效果应该也是一样的……?
export type IRelationships = Record<string, IRelationship>;export type ISimpleDataFormat<T> = {attributes: T & { id: string };relationships?: IRelationships;
};
relationship 我保存了后端传来的数据格式,一来没有特别的复杂,二来对于 create/update 操作,有的时候需要添加 relationship 进行双重验证和建立外链。
Redux 状态的设定
同样,我也对 Redux 存储的状态进行了标准化。首先它需要有一个 loading 状态,这样可以方便 UI 进行状态的更改,同时提醒用户正在拉去数据。其次需要错误的状态,同样也是为了显示在 UI 上,最后需要分别存储 data 和 included。经过讨论,最终决定以 K-V 对的方法存储数据。
最后的状态定义如下:
// 也可以使用 Record,我之后应该会以 Record 为主,毕竟写起来方便些
export type ReduxDataType<T> = { [key: string]: T };export type ISliceStateType<T, U> = {isLoading: boolean;error: null | SerializedError;data: ReduxDataType<T>;included: ReduxDataType<U>;
};export const initialSliceState = {isLoading: false,error: null,data: {},included: {},
};
其中 included 可能会存在多个不同的外链,不过目前我们只用到了一个,所以用 <T, U>,如果之后 included 出现两个以上的外链,那么……再改叭……
slice 的实现
这里主要讲一个 fetch,其他的操作基本一致,没什么特别大的区别。API 方面的话,就用网上的 dummy API 了:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { initialSliceState } from '../sliceType';
import { ISimpleDataFormat, ISliceStateType } from '../../../types';
import { pick } from 'lodash';export type IPost = {body: string;title: string;userId: number;
};export interface IPostModel extends ISimpleDataFormat<IPost> {}export const defaultPost: IPost = {body: '',title: '',userId: 0,
};export const fetchPost = createAsyncThunk('posts/fetch', async () => {return fetch('https://jsonplaceholder.typicode.com/posts').then((response) => response.json()).then((json) => json);
});const postSlice = createSlice({name: 'rdmMarket',initialState: initialSliceState as ISliceStateType<IPostModel, unknown>,reducers: {},extraReducers(builder) {builder.addCase(fetchPost.fulfilled, (state, action) => {state.isLoading = false;action.payload.forEach((data: IPost & { id: string }) => {const id = String(data.id);const pickedAttributes = pick(data, Object.keys(defaultPost)) as IPost;const model: IPostModel = {attributes: {id,...pickedAttributes,},};state.data[id] = model;});});},
});export const postReducer = postSlice.reducer;
效果:


这里的数据相对而言比较简单,因此说使用 pick 对 model 的操作好像有些多余。不过对我们项目来说:
- 后端会自动生成一堆前端用不到的 key,比如说创建时间、更新时间、创建对象、版本等,这些东西前段用不到,存储在状态里就是占用无谓的空间。我们项目数据量还比较大,有的时候会有十几二十来万的数据(2b 项目,非 2c),所以能做一点优化就做一点优化。
- 多余的数据传到后端会被 rejected 掉,所以对数据的过滤是必须的

如果说一些属性是不需要的,直接在 interface 中删掉,TS 就会自动报错了。
类型检查主要还是防止 typo 以及写代码更方便一些,比如说:


我们项目的数据都是强定形的,所以对我们项目来说使用 TS 的优势绝对比使用 JS 多……尤其是有些 entity 会有一百多个 attributes,这时候如果没有一些智能提示或者是类型检查,报错都得掉一堆头发
是的,真的发生过,后来发现就因为是 typo 所以拿不到值……还有就是后端改了一些数据类型,然后前端没有在所有的组件中全部更新,导致有些页面出现问题有些事正常的。也就是那时候我们决定要落实 TS 的实现(虽然到最后只有我一个人在认真想怎么重构,猪队友都在用 any……痛苦……
相关文章:
基于 Redux + TypeScript 实现强类型检查和对 Json 的数据清理
基于 Redux TypeScript 实现强类型检查和对 Json 的数据清理 突然像是打通了任督二脉一样就用了 generics 搞定了之前一直用 any 实现的类型…… 关于 Redux 的部分,这里不多赘述,基本的实现都在这里:Redux Toolkit 调用 API 的四种方式 和…...
HIVE语法优化之Join优化
桶用两表关联字段,MapJoin时需要将小表填入内存,这时候,分桶就起到了作用 一个stage阶段代表一个mr执行,好几个MR,会吧每一个MR的结果都压缩 Mysql 慢查询 如果sql语句执行超过指定时间,定义该sql为慢查询,存储日志, 查问题: SQL日志,模拟慢SQL 然后查询执行计划 分组聚合 就…...
如何申请境内金融信息服务报备
依据《金融信息服务管理规定》等要求,开展境内金融信息服务报备工作事项如下: 一、报备对象及要求 金融信息服务,是指向从事金融分析、金融交易、金融决策或者其他金融活动的用户提供可能影响金融市场的信息和(或者)…...
VS code:Task
Task 微软官方连接: https://code.visualstudio.com/docs/editor/tasks what is Task 我们知道,vscode可以支持许多编程语言,很多语言是需要进行编译的,打包,测试… 有许多已有的工具支持这些流程,例如A…...
《Java-SE-第三十章》之哲学家就餐问题
前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…...
关于接口测试用例设计的一些思考
接口测试发现的典型问题 传入参数处理不当,引起程序错误类型溢出,导致数据读取和写入不一致对象权限校验出错,可获取其他角色信息状态出错,导致逻辑处理出现问题逻辑校验不完善定时任务执行出错 接口测试用例设计 接口测试用例…...
gin和gorm框架安装
理论上只要这两句命令 go get -u gorm.io/gorm go get -u github.com/gin-gonic/gin然而却出现了问题 貌似是代理问题,加上一条命令 go env -w GOPROXYhttps://goproxy.cn,direct 可以成功安装 安装gorm的数据库驱动程序 go get -u gorm.io/driver/mysql...
今天小编继续给大家分享五款高效的电脑宝藏软件
目录 1、keytweak 2、ScreenToGif 3、Greenshot截屏工具 4、GIMP 5、HandBrake 1、keytweak keytweak 简单来说就是一个键盘按键修改器,说白了就是一个键盘按键重映射的软件。比如你键盘上的Q不好用了,你可以更换成一个不常见的按键来代替Q键&#x…...
SQL Server数据库如何添加mysql链接服务器(Windows系统)
SQL Server数据库如何添加mysql链接服务器(Windows系统) 一、说明二、下载mysql的odbc驱动三、安装mysql odbc四、配置ODBC4.1 控制面板→ODBC数据源(64位)→双击打开4.2 添加msql odbc数据源 五、测试添加是否成功六、打开SSMS&a…...
scala连接mysql数据库
scala中通常是通过JDBC组件来连接Mysql。JDBC, 全称为Java DataBase Connectivity standard。 加载依赖 其中包含 JDBC driver <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29&l…...
datax-web登陆时出现账号密码错误
在查找问题时,在admin里面查看日志时: 目录的位置:datax-web-2.1.2/modules/datax-admin/bin/console.out 发现了java程序没有跑起来,解决对应的bug问题即可,一般都是数据库连接的问题,可能和使用的数据库版…...
Redis 和 MySQL如何保证数据一致性
场景分析 Redis 用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库 IO ,还可以提升数据的 IO 性能。当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就 直接返回。如果没有命中…...
VR虚拟仿真技术在道路桥梁中有哪些具体应用?
虚拟现实(VR)是一种新兴的技术,可以为桥梁工程提供许多应用场景。以下是一些可能的应用场景: 1.桥梁设计和模拟 VR元宇宙可以用于桥梁的设计和模拟。工程师可以使用VR技术来创建桥梁的三维模型,并对其进行测试和优化。这可以帮助工程师更好地…...
如何找到死锁的线程?_java都学什么
在Java中,死锁是指两个或多个线程被无限地阻塞,等待彼此持有的资源,从而导致程序无法继续执行的情况。死锁通常是由于线程之间循环等待资源而产生的。要找到死锁的线程,可以采用以下方法: 1.线程转储(Thread Dump) 通过…...
MFC遍历目录包括子目录下所有文件、特定类型文件
文章目录 用法实现遍历所有文件遍历所有txt文件用法 vector<CString> v; //获取所有文件 GetFilePath(v,L"D:\\test\\"); //文件路径储存在容器里面,遍历容器 for(int i=0...
Kubernetes 集群calico网络故障排查思路
报错calico/node is not ready: BIRD is not ready: BGP not established with 172.16.0.20,172.16.0.30 \\calico未准备好,BGP协议不能与172.16.0.20,172.16.0.30内网IP地址连接 BGP协议:边界网关协议 访问k8s的dashboard界面无法访问网站,查看pod&am…...
OBS视频视频人物实时扣图方法(四种方式)
图片擦除一些杂乱图像 参考:https://www.bilibili.com/video/BV1va411G7be https://github.com/Sanster/lama-cleaner第一种:色度键选项 第二种:浏览器建立窗口选项 参考视频:https://www.bilibili.com/video/BV1WS4y1C7QY http…...
DROP USER c##xyt CASCADE > ORA-01940: 无法删除当前连接的用户
多创建了一个用户,想要给它删除掉 一 上执行过程,确实删除成功了 Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production With the Partitioning, OLAP, Advanced Analytics and Real Application Testing optionsSQL> DR…...
【JAVA】-【IO流】
文章目录 FileReader读入数据的基本操作FileReader中使用reader()FileWrite写出数据的操作使用FileInputStream、FileOutputStream操作图片缓冲流(字节型)实现非文本文件的复制 复制文本文件也可以使用字节流,但是不要在内存中读出来…...
PoseFormer:基于视频的2D-to-3D单人姿态估计
3D Human Pose Estimation with Spatial and Temporal Transformers论文解析 摘要1. 简介2. Related Works2.1 2D-to-3D Lifting HPE2.2 GNNs in 3D HPE2.3 Vision Transformers 3. Method3.1 Temporal Transformer Baseline3.2 PoseFormer: Spatial-Temporal TransformerSpati…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
GC1808:高性能音频ADC的卓越之选
在音频处理领域,高质量的音频模数转换器(ADC)是实现精准音频数字化的关键。GC1808,一款96kHz、24bit立体声音频ADC,以其卓越的性能和高性价比脱颖而出,成为众多音频设备制造商的理想选择。 GC1808集成了64倍…...
运动控制--BLDC电机
一、电机的分类 按照供电电源 1.直流电机 1.1 有刷直流电机(BDC) 通过电刷与换向器实现电流方向切换,典型应用于电动工具、玩具等 1.2 无刷直流电机(BLDC) 电子换向替代机械电刷,具有高可靠性,常用于无人机、高端家电…...
实现p2p的webrtc-srs版本
1. 基本知识 1.1 webrtc 一、WebRTC的本质:实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力,二者在架构设计和功能定位上高度相似: 分层协议栈架构 Linux网络协议栈:从底层物理层到应用层(如…...
