前言

在项目内,使用TypeScript的过程中遇到了一些状况、报错或棘手的情况,决定开一个系列记录遇到的问题和我的解决方法。

背景

在项目开发中,很多时候会遇到一种场景,需要定义一个对象的类型,此类型必须包含某n个字段中的其中一种。

例如,我要定义一个工程师(Engineer)的对象,里面包括姓名(name),性别(gender),年龄(age)和一门编程语言(java/cpp/go/js四选一)的评价。

显然,前三个字段都是很简单的,但是第四个就有点麻烦了。首先,第四个字段的key是可以不一样(甚至value也有可能不同),其次字段只能从给定的里面4选1。

初步方案

一开始是考虑使用可选或联合类型,但是发现没有办法进行4选1的限制,对于没有编程语言字段,或者多个编程语言字段的情况并没有很好的限制。最后只能使用泛型,再使用时进行显式的声明。

于是,类型定义如下:

interface ICodingLangRating {
    java: string
    cpp: string
    go: string
    js: string
}

type Engineer<K extends keyof ICodingLangRating> = {
    name: string
    gender: 'male' | 'female'
    age: number
} & Pick<ICodingLangRating, K>

对该声明的校验代码如下:

// 正确
const candidate: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    java: 'fabulous'
}

// 错误,声明了java,但是却同时定义了java和go字段
const candidate_1: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    java: 'fabulous',
    go: 'not bad'
}

// 错误,声明了java,但是类型不正确
const candidate_2: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    java: 666
}

// 错误,声明了java,但是却定义了go字段
const candidate_3: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    go: 'not bad'
}

// 错误,声明了java,但是却同时定义了cpp和go字段
const candidate_4: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    cpp: 'unknown',
    go: 'not bad'
}

// 错误,声明了java,但是却没有定义java字段
const candidate_5: Engineer<'java'> = {
    name: 'Jack',
    gender: 'male',
    age: 22
}

// 错误,声明了ICodingLangRating中不存在的python
const candidate_6: Engineer<'python'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    python: 'just so so'
}

从校验代码可以看出,针对各种不符合期望的情况:

  • 声明a,却定义a和b
  • 声明a,但定义a的类型不正确
  • 声明a,却定义b
  • 声明a,却定义b和c
  • 声明a,却没有定义a
  • 声明不合法的f

都能做出正确的限制,确保在业务场景的代码中,有且只有一个合法范围的字段。

但是,转折来了!

在后来的使用中,我们发现,其实这个解决方案只是一个弱限制,如果在泛型的显式声明中,传入联合类型的话,那还是可以绕过有且只有一个编程语言字段的限制。

// 正确,声明了java和go,并同时定义了java和go字段
const candidate_1: Engineer<'java' | 'go'> = {
    name: 'Jack',
    gender: 'male',
    age: 22,
    java: 'fabulous',
    go: 'not bad'
}

难道就真的没有办法做到只能选择一个的限制么?

终极方案

根据上面的尝试,目前我们还缺少的是如何阻止同时有2个或以上的合法字段出现。最笨的方式就是为每一个语言都定义一个类似{ langName: string }这样的类型然后通过extends或者联合类型使用,但是显然这样就没有办法做到在其它情况通用。

而通过官方现成的工具类型,由于都是支持字面量和联合类型,没有办法筛选出只包含一个字段的类型。就在这时,我想到,是不是可以定义出一个类型,包含全部字段,但是只有一个字段是正确有意义,其他字段都是无意义的呢。

最终,我就构造出下面这个PickOne工具类型:

type PickOne<T> = {
    [K in keyof T]: Record<K, T[K]> & Partial<Record<Exclude<keyof T, K>, undefined>>
}[keyof T]

测试代码如下:

type OneLang = PickOne<ICodingLangRating>

// 正确
const lang: OneLang = {
    java: 'good'
}

// 错误
const lang2: OneLang = {
    python: 'unknown'
}

// 错误
const lang3: OneLang = {
    java: 'good',
    go: 'good'
}

// 错误
const lang4: OneLang = {
    java: 123
}

最后,类型定义代码如下:

interface ICodingLangRating {
    java: string
    cpp: string
    go: string
    js: string
}

type Engineer = {
    name: string
    gender: 'male' | 'female'
    age: number
} & PickOne<ICodingLangRating>

使用了这个PickOne工具类型,我不需要在使用的时候显式的指定编程语言,甚至还能在其它类似的场景使用。

总结

到此这篇关于TypeScript开发小状况记录之选且只选一个的文章就介绍到这了,更多相关TypeScript选且只选一个内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

TypeScript开发小状况记录之选且只选一个的更多相关文章

  1. 掌握JDK1.5枚举类型

    2.所有枚举值都是public,static,final的。1.遍历所有有枚举值.知道了有values方法,我们可以轻车熟路地用ForEach循环来遍历了枚举值了。*.*/privatestaticintnumber=Color.values().length;/***随机返回一个枚举值@returnarandomenumvalue.*/publicstaticColorgetRandomColor(){longrandom=System.currentTimeMillis()%number;switch

  2. 使用typescript类型实现ThreeSum

    这篇文章主要介绍了使用typescript类型实现ThreeSum,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以一下,希望对你学习又是帮助

  3.  typeScript入门基础介绍

    这篇文章主要介绍了 typeScript入门基础,TypeScript 是由微软开发的开源、跨平台的编程语言,是 javaScript 的超集,最终被编译为 javaScript代码。常常被简称为TS支持JS、ES语法,下文将继续其他基础介绍,需要的朋友可以参考一下

  4. typescript返回值类型和参数类型的具体使用

    本文主要介绍了typescript返回值类型和参数类型的具体使用文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. JavaScript数值类型知识汇总

    这篇文章主要给大家介绍了关于JavaScript数值类型知识汇总的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用JavaScript具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

  6. python内建类型与标准类型

    这篇文章主要介绍了python内建类型与标准类型,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

  7. Vue3 携手 TypeScript 搭建完整项目结构

    TypeScript 是JS的一个超级,主要提供了类型系统和对ES6的支持,使用 TypeScript 可以增加代码的可读性和可维护性,在 react 和 vue 社区中也越来越多人开始使用TypeScript,这篇文章主要介绍了Vue3 携手 TypeScript 搭建完整项目结构,需要的朋友可以参考下

  8. Android图片或拍照选择图片功能实例代码

    这篇文章主要给大家介绍了关于Android图片或拍照选择图片功能的相关资料,文中通过示例代码介绍的非常详细,对各位Android开发者具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

  9. 关于TypeScript开发的6六个实用小技巧分享

    TypeScript是Javascrip t超集,支持静态类型检测,可以在编译期提前暴露问题,节省debug时间,下面这篇文章主要给大家介绍了关于TypeScript开发的6六个实用小技巧,需要的朋友可以参考下

  10. typescript+react实现移动端和PC端简单拖拽效果

    这篇文章主要为大家详细介绍了typescript+react实现移动端和PC端简单拖拽效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部