TypeScript开发小状况记录之选且只选一个

 更新时间:2022年10月8日 10:12  点击:408 作者:Rio_Kwok

前言

在项目内,使用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选且只选一个内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://juejin.cn/post/7150316226009382919

[!--infotagslink--]

相关文章

  • php中浮点型(float)和整型(integer)数据类型详解

    文章分析了关于php中浮点型(float)和整型(integer)数据类型的用法区别以及在那种情况下会出现数据长度不够。 取值只能为True或者False,当其他类型转化为boolean类...2016-11-25
  • c# 数据类型占用的字节数介绍

    本篇文章主要是对c#中数据类型占用的字节数进行了详细的介绍。需要的朋友可以过来参考下,希望对大家有所帮助...2020-06-25
  • Javascript类型转换的规则实例解析

    这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
  • vue Treeselect下拉树只能选择第N级元素实现代码

    这篇文章主要介绍了vue Treeselect下拉树只能选择第N级元素实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
  • 浅谈TypeScript 索引签名的理解

    这篇文章主要给大家分享的是TypeScript 索引签名的理解,索引签名由方括号中的索引名称及其类型组成,后面是冒号和值类型:{ [indexName: KeyType]: ValueType }, KeyType 可以是一个 string、number 或 symbol,而ValueType 可以是任何类型,下面就俩简单了解一下吧...2021-10-15
  • 深入理解PHP变量的值类型和引用类型

    在PHP中,大部分变量类型,如字符串,整型,浮点,数组等都是值类型的,而类和对象是引用类型,在使用的时候,需要注意这一点。看到网友在讨论PHP的&符号,要彻底理解它的用法,就有必要讨论一下变量的两种形式。PHP的变量在内存中是这样...2015-10-23
  • JQuery.Ajax()的data参数类型实例详解

    假如现在有这样一个表单,是添加元素用的。<form id='addForm' action='UserAdd.action' type='post'> <label for='uname'>用户名</label>:<input type='text' name='uname' id='uname'><br>...2015-11-24
  • Javascript中的数据类型之旅

    虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number、String、Boolean、Object、Udefined、Null。其中,Object属于复杂数据类型,Object 由无序的键值对组成。其余几种都属于简单数据类型。注意:变量...2015-10-21
  • C#泛型类型知识讲解

    这篇文章主要介绍了C#泛型类型知识,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-06-25
  • java中String类型变量的赋值问题介绍

    下面小编就为大家带来一篇java中String类型变量的赋值问题介绍。小编觉得挺不错的。现在分享给大家,给大家一个参考。...2016-03-28
  • vue:el-input输入时限制输入的类型操作

    这篇文章主要介绍了vue:el-input输入时限制输入的类型操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-05
  • JavaScript实现数据类型的相互转换

    这篇文章主要为大家详细介绍了JavaScript实现数据类型的相互转换,感兴趣的朋友可以参考一下...2016-03-09
  • C#的四种基本数据类型

    本文主要介绍了C#的四种基本数据类型的相关知识,具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • vue cli4.0项目引入typescript的方法

    这篇文章主要介绍了vue cli4.0项目引入typescript的方法,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-17
  • 基于PostgreSQL和mysql数据类型对比兼容

    这篇文章主要介绍了基于PostgreSQL和mysql数据类型对比兼容,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-12-25
  • js判断浏览器类型,版本的代码(附多个实例代码)

    在网站前端开发中,浏览器兼容性问题本已让我们手忙脚乱,Chrome的出世不知道又要给我们添多少乱子。浏览器兼容性是前端开发框架要解决的第一个问题,要解决兼容性问题就得首先准确判断出浏览器的类型及其版本。 JavaScrip...2014-05-31
  • C#与C++之间类型的对应知识点总结

    这篇文章主要介绍了C#与C++之间类型的对应知识点总结,对此有需要的朋友们可以参考下。...2020-06-25
  • 详解Vue3.0 + TypeScript + Vite初体验

    这篇文章主要介绍了详解Vue3.0 + TypeScript + Vite初体验,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-22
  • R语言中的因子类型详解

    这篇文章主要介绍了R语言中的因子类型详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-05-06
  • 使用TS来编写express服务器的方法步骤

    这篇文章主要介绍了使用TS来编写express服务器的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30