如何利用 TypeScript 的判别联合类型提升错误处理与代码安全性

5e2cccaccd9f74d73060b25d866b79d7.jpeg

欢迎回到 TypeScript 高级技巧系列文章。我们之前已经讨论了Extract、Exclude和Indexed Access Types,接下来我们将深入探讨我最喜欢的TypeScript特性之一:判别联合类型(Discriminated Unions)。为什么我如此钟爱它呢?因为我很懒,我更喜欢通过TypeScript的类型系统和智能提示(IntelliSense)来回答我当前光标所在位置的问题:"如果是这种情况,我可以访问哪些属性?"

在处理庞大的代码库时,这个功能尤其方便。因为我最不想做的事情就是打开另一个文件,逐个查看属性和条件,确认自己可以访问哪些属性。而即使我认为可以访问某个属性,也希望在生产环境中确保这个假设不会给我带来麻烦。

让我们通过这篇文章,深入了解如何从判别联合类型中提取类型,进一步提升我们的编码效率和代码可靠性。

相关文章推荐

  • 如何利用 TypeScript 的 Extract 提升类型定义与代码清晰度

  • 如何利用 TypeScript 的 Exclude 提升状态管理与代码健壮性

  • TypeScript 进阶,深入理解并运用索引访问类型提升代码质量

什么是判别联合类型?TypeScript中的魔法衣橱整理术

在TypeScript中,判别联合类型(Discriminated Unions)使用一个共同的属性,称为判别属性(discriminant),来区分联合类型中的不同类型。简单来说,想象一下你打开衣柜,看到各种类型的衣物。你有上衣和下装。如果没有判别联合类型,我们可能会这样组织:

type Clothing = {material: string;sleeveLength?: 'short' | 'long';length?: 'short' | 'long';type?: 'top' | 'bottom';
};

这种方法在你只需要处理一次条件时是可行的。然而,当你再次处理这些类型时,很快就会发现问题重重。这就像一个你不想打开的混乱衣柜。“妈,告诉我怎么整理这个乱七八糟的衣柜,这样下次我就能找到我的运动裤了!”

如果你没有问这个问题,你还没有准备好接受答案。

有了判别联合类型,你可以恢复衣柜的秩序。现在,你可以这样分类你的衣服:

type Top = { type: 'top';material: string;sleeveLength: 'short' | 'long';
};type Bottom = {type: 'bottom';material: string;length: 'short' | 'long';
};type Clothing = Top | Bottom;

在这个例子中,type属性就是判别属性。它清楚地标识了一件衣物是“上衣”还是“下装”。当你使用Clothing类型时,TypeScript的类型系统可以使用这个判别属性来缩小类型范围,并根据是Top还是Bottom提供更具体的信息或检查。

例如,如果你从Clothing联合类型中访问一个项目,TypeScript会知道如果type是'top',那么这个项目还会有sleeveLength属性;如果type是'bottom',它将有length属性。这简化了不同类型的管理,增强了代码的安全性和清晰度。

现在,你可以轻松找到你的时尚短裤,它们在标有“Bottom: short”的第三个抽屉里。

通过这种方式,判别联合类型不仅让代码更加简洁明了,也让你在处理复杂类型时更加得心应手。

基础示例:消息应用程序中的判别联合类型

好吧,现在我们来点正经的。我们想要构建解决方案,而不仅仅是整理衣柜。考虑一个消息应用程序的场景,其中消息可以是文本、图片或系统通知。我们使用type属性作为判别属性,以清晰地区分这些消息类型:

type Message =  | { type: 'text'; content: string; sender: string }  | { type: 'image'; src: string; caption?: string }  | { type: 'system'; event: string };

当处理消息时,如果我们能立刻识别出正在处理的消息类型,是不是很方便?让我们看看使用判别联合类型能做些什么:

function displayTextMessage(content: string, sender: string) {  console.log(`来自${sender}的文本消息: ${content}`);  
}  function displayImageMessage(src: string, caption?: string) {  console.log(`图片来源: ${src}`, `图片描述: ${caption ?? '无描述'}`);  
}  function handleSystemEvent(event: string) {  console.log(`系统事件: ${event}`);  
}  function handleMessage(message: Message) {  switch (message.type) {  case 'text':  // TypeScript 现在知道 `message` 是 `{ type: 'text'; content: string; sender: string }` 类型  displayTextMessage(message.content, message.sender);  break;  case 'image':  // `message` 现在是 `{ type: 'image'; src: string; caption?: string }` 类型  displayImageMessage(message.src, message.caption);  break;  case 'system':  // `message` 是 `{ type: 'system'; event: string }` 类型  handleSystemEvent(message.event);  break;  }  
}

正确用法:

const sampleMessage: Message = { type: 'text', content: '你好,TypeScript!', sender: 'User123' };  
handleMessage(sampleMessage); 
// "来自User123的文本消息: 你好,TypeScript!"

错误用法:

handleMessage({ type: 'text', sender: 'User123' }); 
// TypeScript 类型错误:参数 `{ type: "text"; sender: string; }` 不可赋值给 `Message` 类型。

再举一个错误用法的例子:

handleMessage({ type: 'system', src: 'image.png', caption?: '看这张不同的裤子' }); 
// TypeScript 类型错误:对象文字可能只能指定已知属性,且 `src` 不存在于 `{ type: "system"; event: string; }` 类型中。

在第一个用法中,TypeScript不会报错,因为参数完全符合Message类型。然而,在第二个用法中,TypeScript会报错,因为缺少content属性,而content属性对于文本消息来说是必需的。最后,在第三个例子中,我们错误地将系统消息的属性与图片消息的属性混淆,导致类型错误。

在handleMessage函数中,TypeScript像一个敏锐的分类器。它检查每种情况下的消息类型,整齐地分类它们。这就像把你的消息分到不同的文件夹中:文本、图片、系统警报,确保我们只操作每种消息类型所对应的属性,准确地避免那些常见的运行时错误。就像确保你不会意外地给照片加上文字描述或给文本消息加上图片描述一样,保持整洁和无错误!

进阶示例:服务器端错误处理

现在,让我们看看一个更高级的解决方案:在服务器端应用程序中的错误处理。这是一个简化的示例,但其概念来自于我目前正在开发的真实应用程序。问题简述如下:随着最近Next.js的开发,我们需要对应用程序的服务器端逻辑进行一些重构。这次重构带来了一个独特的挑战,特别是在处理不同类型的错误方面。所以,我认为可以用这个例子来展示判别联合类型在实际场景中的实用性。

在像Next.js这样的服务器应用程序中,处理不同类型的错误(如ConflictError、UnauthorizedError和ValidationError)是至关重要的。判别联合类型允许我们以结构化和类型安全的方式管理这些错误。

interface ConflictError extends Error {type: 'ConflictError';
}interface UnauthorizedError extends Error {type: 'UnauthorizedError';
}  interface ValidationError extends Error {type: 'ValidationError';details: string;
}// 服务器错误的联合类型
type ServerError = ConflictError | UnauthorizedError | ValidationError;const createConflictError = (message: string): ConflictError => {const error = new Error(message) as ConflictError;error.type = 'ConflictError';return error;
};const createUnauthorizedError = (message: string): UnauthorizedError => {const error = new Error(message) as UnauthorizedError;error.type = 'UnauthorizedError';return error;
};const createValidationError = (message: string, details: string): ValidationError => {const error = new Error(message) as ValidationError;error.type = 'ValidationError';error.details = details;return error;
};function handleServerError(error: ServerError) {switch (error.type) {case 'ConflictError':console.error(`冲突错误: ${error.message}`);break;case 'UnauthorizedError':console.error(`未授权错误: ${error.message}`);break;case 'ValidationError':console.error(`验证错误: ${error.message}, 详情: ${error.details}`);break;default:console.error(`未处理的服务器错误: ${error}`);}
}// 模拟服务器操作
function simulateServerAction(action: string): void {switch (action) {case 'updateUsername':throw createValidationError('无效的用户名', '用户名太短');case 'deleteUser':throw createUnauthorizedError('用户无权删除此账户');default:throw createConflictError('用户已存在');}
} // 测试服务器操作中的错误处理
try {simulateServerAction('updateUsername');
} catch (error) {if (error instanceof Error && 'type' in error) {handleServerError(error as ServerError);} else {console.error('发生未知错误', error);}
}

这个高级示例展示了在服务器应用程序中使用判别联合类型进行错误处理的有效方法。通过定义不同的服务器错误类型并使用工厂函数,我们创建了一种结构化且易于管理的错误处理方法。

handleServerError函数利用TypeScript的类型检查来准确处理不同的错误类型,从而提高代码的可读性和可维护性。

这个示例不仅展示了判别联合类型在处理复杂逻辑时的强大功能,也强调了TypeScript在提高代码质量方面的重要作用。

结束

在TypeScript中,判别联合类型提供了一种简化处理复杂场景的方法。这个特性不仅增强了代码的清晰度,还与逻辑决策过程紧密结合。它是一个实用的工具,为我们的代码带来了精确性,确保联合类型的每一部分都能根据其独特的特性得到正确处理。我们会问,“这是什么类型的消息?”并做出相应的处理。TypeScript的判别联合类型让我们可以以这种逻辑思维方式进行编码,这体现了该语言的设计如何与人类的思维过程相契合。

本文展示了判别联合类型在TypeScript中的实用性和重要性,特别是在复杂状态管理和处理多样数据结构方面。通过利用判别联合类型,我们可以编写出更可预测、更抗错误的代码。

随着这个系列的继续,我们将进一步探讨TypeScript的高级特性,提升我们的技能和理解。请继续关注我对TypeScript功能的深入探讨,帮助你在项目中充分利用这门强大的语言。

再次感谢阅读,我们下篇文章见!👋

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854038.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ROS里程计】中部分代码解释

bool OdomNodePub::Odom_Reset(ubt_odom::odomreset::Request& req, ubt_odom::odomreset::Response& res) {if(req.cmd "reset"){OdomResetFlag true;}else{OdomResetFlag false;}res.state "success";return true; } 该函数是一个ROS节点中…

【Mybatis-Plus】根据自定义注解实现自动加解密

背景 我们把数据存到数据库的时候,有些敏感字段是需要加密的,从数据库查出来再进行解密。如果存在多张表或者多个地方需要对部分字段进行加解密操作,每个地方都手写一次加解密的动作,显然不是最好的选择。如果我们使用的是Mybati…

边缘微型AI的宿主?—— RISC-V芯片

一、RISC-V技术 RISC-V(发音为 "risk-five")是一种基于精简指令集计算(RISC)原则的开放源代码指令集架构(ISA)。它由加州大学伯克利分校在2010年首次发布,并迅速获得了全球学术界和工…

嵌入式门槛高不高,工资怎么样?

一般来说,嵌入式岗位的准入门槛其实并不是特别高。通常情况下,只要能够熟练掌握 C 语言编程以及单片机相关知识,就能够去制作一些较为简单的电子产品,由此可见其门槛相对而言是比较低的,相应的薪水可能也不会特别高。 …

数据库-数据定义和操纵-初始MySQL数据库

连接数据库: mysql -u用户名 -p密码 创建数据库: create database 数据库名; 命令查看MySQL中已存在的数据库 show database; 数据库中创建表的规则: CREATE TABLE 表名 (字段名,数据类型,字段名,数据类型,..... ) eg: 首先创建数据库&am…

计算机专业:黄金时代是否依旧?

计算机专业:黄金时代是否依旧? 随着2024年高考落幕,数百万高三学生将面临人生中的重要抉择:选择大学专业。在这个关键节点,计算机相关专业是否仍是“万金油”的选择?在过去的几十年里,计算机科…

fetch_lfw_people()报错urllib.error.HTTPError: HTTP Error 403: Forbidden的解决方案

零、实验报告地址 计算机视觉实验二:基于支持向量机和随机森林的分类(Part one: 编程实现基于支持向量机的人脸识别分类 )-CSDN博客 一、代码报错 fetch_lfw_people()报错urllib.error.HTTPError: HTTP Error 403: Forbidden 二、报错原因 通常是由于访问权限不足导致的…

Mysql中索引详解

1、什么是索引 在日常学习中,最常见使用索引的例子就是词典,通过对字母进行排序,并设置对应的页数,从而循序定位某个单词,除了词典,如火车站的车次表、图书的目录等都是使用了索引。它们的原理都是一样的&…

C++11参数包...Args

以list中的包装器做介绍 包装器是由一个类模板接收后存储在统一的...Args中 标准格式 说明&#xff1a;...Args就是参数包的类型 实例&#xff1a; //参数包 void Show() {cout <<"结束" << endl; }template<class T,class ...Args> void Show(T…

LabVIEW与C#的区别及重新开发自动测试程序的可行性分析

LabVIEW和C#是两种广泛使用的编程语言&#xff0c;各自有不同的应用领域和特点。本文将详细比较LabVIEW与C#在自动测试程序开发中的区别&#xff0c;并分析将已完成的LabVIEW自动测试程序重新用C#开发的合理性。本文帮助评估这种转换的必要性和潜在影响。 LabVIEW与C#的区别 开…

C++编程:vector容器的简单模拟实现

前言&#xff1a; 在C标准库&#xff08;STL&#xff09;中&#xff0c;vector容器是最常见使用的动态数组。它结合了链表与数组的优点&#xff0c;提供了灵活的大小调整与高效的随机访问。本文将简单的对vector容器进行介绍并且对vector容器简单的模拟实现。 一、vector的文…

uniapp实现路由拦截——实战案例(二)

uniapp如何实现登录路由拦截&#xff1f; 今天再次介绍一下 uni-simple-router 插件&#xff0c;记得最初使用时&#xff0c;是在三年以前了&#xff0c;这里简单介绍通过自动读取 pages.json 作为路由表的方式&#xff0c;欢迎指教~ 文章目录 uniapp如何实现登录路由拦截&…

LangChain入门学习笔记(二)——LangChain表达式语言(LCEL)

基于LangChain框架编写大模型应用的过程就像垒积木&#xff0c;其中的积木就是Prompts&#xff0c;LLMs和各种OutputParser等。如何将这些积木组织起来&#xff0c;除了使用基本Python语法调用对应类的方法&#xff0c;一种更灵活的方法就是使用位于LangChain-Core层中的LCEL&a…

SwiftUI 6.0(Xcode 16)全新 @Entry 和 @Previewable 宏让开发妙趣横生

概览 如火如荼的 WWDC 2024 已进入第五天&#xff0c;苹果开发平台中众多海量新功能都争先恐后的喷薄欲出。 在这里就让我们从中挑两个轻松有趣的新功能展示给小伙伴们吧&#xff1a;它们分别是 全新的 Entry 和 Previewable 宏。 在本篇博文中&#xff0c;您将学到如下内容&a…

【C++ 11 新特性】lambda 表达式详解

文章目录 1. 常见 lambda 面试题&#x1f58a; 1. 常见 lambda 面试题&#x1f58a; &#x1f34e;① 如果⼀个 lambda 表达式作为参数传递给⼀个函数&#xff0c;那这个函数可以使⽤这个 lambda 表达式捕获的变量吗 ? &#x1f427; 函数本身无法直接访问到 lambda表达式捕获…

vue3实现表格的分页以及确认消息弹窗

表格的分页实例展示 效果1:表格按照每行10条数据分页,且编号也会随之分页自增 实现按照页码分页效果 第二页 展示编号根据分页自动增长 固定表格高度 这边设置了滚动条,同时表格高度实现自适应滚动条高度 template部分 表格代码 编号是按照页码条数进行循环并根据索引自增…

力扣191. 位1的个数

Problem: 191. 位1的个数 文章目录 题目描述思路复杂度Code 题目描述 思路 题目规定数值的范围不会超过32位整形数 1.定义统计个数的变量oneCount&#xff1b;由于每次与给定数字求与的变量mask初始化为1 2.for循环从0~32&#xff0c;每一次拿mask与给定数字求与运算&#xff…

【Linux应用】Linux系统的设备管理——Udev

1.udev概述 udev是 Linux2.6内核里的一个功能&#xff0c;它替代了原来的 devfs&#xff0c;成为当前 Linux 默认的设备管理工具&#xff0c;能够根据系统中的硬件设备的状态动态更新设备文件&#xff0c;包括设备文件的创建&#xff0c;删除等。 udev以守护进程的形式运行&am…

YOLOv10的使用总结

目录 YOLOv10介绍 部署和使用示例 微调训练 YOLO模型因其在计算成本和检测性能之间的平衡而在实时目标检测中很受欢迎。前几天YOLOv10也刚刚发布了。我们这篇文章就来看看YOLOv10有哪些改进&#xff0c;如何部署&#xff0c;以及微调。 概述 实时物体检测旨在以较低的延迟准…

CSS 实现电影信息卡片

CSS 实现电影信息卡片 效果展示 CSS 知识点 CSS 综合知识运用 页面整体布局 <div class"card"><div class"poster"><img src"./poster.jpg" /></div><div class"details"><img src"./avtar…