TS 之 Union, Enum, Tuple 运用自如指南 您所在的位置:网站首页 ts图中的s TS 之 Union, Enum, Tuple 运用自如指南

TS 之 Union, Enum, Tuple 运用自如指南

2023-10-24 11:41| 来源: 网络整理| 查看: 265

前言

学习TypeScript有两个难点。一个是了解ts的语法。目前ts的语法特性很多,一些看起来复杂的问题,如果你了解了语法就会发现其实很简单,你只是不知道解决问题的对应语法特性而已。第二是了解类型特性。ts中的enum、union、tuple等类型提供了不同角度的类型声明方式。这些类型中有些是彼此之间相似程度比较高,有些也可以相互转化。了解这些类型越全面,对我们解决typescript中一些难题很有帮助。

TypeScript Basic Types

Tuple和Enum属于TypeScriptBasic Types中的一个。因为特性比较特殊,因此值得深入了解。

Boolean

Number

String

Array

Tuple

Enum

Unknown

Any

Void

Null and Undefined

Never

Object

Tuple

定义元组的方式很简单。

// Declare a tuple type let x: [string, number]; // Initialize it x = ["hello", 10]; // OK // Initialize it incorrectly x = [10, "hello"]; // Error type NestedTuple = [string, Array, [boolean, {name: string}]] // 可以定义复杂的tuple Tuple -> Union

Tuple转成Union很多见,简单写就是

type Tuple = [string, number, boolean] type Union = Tuple[number] // string | number | boolean

抽成utility type则会是

type Tuple2Union = Tuple[number] type Tuple = [string, number, boolean] type Union = Tuple2Union

反过来 Union到Tuple基本上不会用到,所以忽略。

转换的具体场景比较多,一般在迭代的时候会用到这个技巧。 例如:我们想要通过tuple定义的类型map出一个对象。

type Tuple = ["blue", "yellow", "red"] // 想要转换出{ blue: string, yellow: string, red: string }的一个结构体 type MappedTuple = { [k in Tuple[number]]: string }

题外话 始终注意type中extends的对象。要分清楚哪个extends哪个

type Includes = P extends T[number] ? true: false // correct! type Includes = T[number] extends P ? true: false // incorrect! type isPillarMen = Includes // expected to be `false` Enum

枚举是一种为一组数值提供更友好名称的方法。

enum Color { Red = 1, Green, Blue, } let c: Color = Color.Green;

enum的key得是string,其value要么是string,要么是number 这两个有一些区别,分开讨论。

1.数字枚举 enum Color { Red = 1, Green, Blue, } 2.字符串枚举 enum CardinalDirection { North = 'N', East = 'E', South = 'S', West = 'W', } 3.混合枚举 enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES", }

简而言之不要这么用。

特性1 不仅是type,还可以作为值来用 enum Color { Red = 1, Green, Blue, } 上面👆定义的Color枚举 compile过后值为 { 1: "Red", 2: "Green", 3: "Blue", Blue: 3, Green: 2, Red: 1, } let colorName: string = Color[2]; console.log(colorName); // 'Green' enum CardinalDirection { North = 'N', East = 'E', South = 'S', West = 'W', } 上面👆定义的CardinalDirection枚举 compile过后值为 { East: "E", North: "N", South: "S", West: "W", } 注意数字枚举和字符串枚举最终值是不一样的。 enum完全可以作为object来用! 特性2 loose type-checking

数字枚举 会有loose type-checking的问题。举个例子

const color1: Color = 4 // Ok const color2: Color.Red = 5 // Ok const color3: Color ='6' // Error

我们期待的是上面三个表达式都会报错,但实际情况是只有最后一个会报type error错误。 造成这个问题的原因见 这里 。因此建议在使用enum的时候 尽量使用字符串枚举或者是坚决避免写如下的代码。

enum Color { Red = 1, Green, Blue, } const value1: Color = 3 // 不要这么写! const value2: Color = Color.Blue // 正确写法 function foo(arg: Color) { if (arg === 1) { // 不要这么写! } if (arg === Color.Red) { // 正确写法 } }

不过有意思的是当我们需要通过Color枚举创建一个ColorMap的时候,type-checking是正常的。

const ColorMap: { [key in Color]: string; } = { 1: 'red color', 2: 'green color', 3: 'blue color', 4: 'x' // Error! }

这里下标为4时typescript会报错。

const enum 和 declare const

可以使用 const enum 的方式创建枚举。

const enum Direction { Up, Down, Left, Right, } let directions = [ Direction.Up, Direction.Down, Direction.Left, Direction.Right, ]; // let directions = [ // 0 /* Up */, // 1 /* Down */, // 2 /* Left */, // 3 /* Right */, // ]; const value = Direction[Direction.Up] // 报错!

编译过后的版本如上,和不加const的区别是加了const编译过后Direction枚举不会作为值存在。所有以值的方式使用的地方都会转成对应的枚举值。这时候不能再将枚举当作object来使用了。

declare enum 的方式定义的枚举不能作为值来使用。

declare enum Direction { Up, Down, Left, Right, } let directions = [ Direction.Up, Direction.Down, Direction.Left, Direction.Right, ];

上面的这种方式,typescript虽然不会显式的报错,但是typescript编译会失败。简而言之也不要这么使用枚举。

Enum -> Union

由于Enum有key,value两套东西,这里分别做个介绍。

EnumKey组成Union

经常有需要获取enum的key的union的场景。enum由于是有值的,因此转Union的过程和将对象的key转成union的场景一致。

enum CardinalDirection { North = 'N', East = 'E', South = 'S', West = 'W', } const DirectionObj = { North: 'N', East: 'E', South: 'S', West: 'W', } type Type1 = keyof typeof CardinalDirection // "North" | "East" | "South" | "West" type Type2 = keyof typeof DirectionObj // "North" | "East" | "South" | "West"

数字枚举也一样,转的过程中不会出现数字枚举

enum Direction { Up, Down, Left, Right, } type Type = keyof typeof Direction // "Up" | "Down" | "Left" | "Right" // 不会出现 0 | 1 | 2 | 3 | "Up" | "Down" | "Left" | "Right" // 至于为啥会觉得可能出现这种type,你可以再往上看看数字枚举和字符串枚举的区别 EnumValue 组成Union

如何通过下面这个枚举获得 "N" | "E" | "S" | "W"这Union呢?

enum CardinalDirection { North = 'N', East = 'E', South = 'S', West = 'W', }

方法也是有的,使用typescript的模版字符串。

type ValueUnion = `${CardinalDirection}` // "N" | "E" | "S" | "W" Union

union也很好理解,就是多个类型的“或”

function printId(id: number | string) { console.log("Your ID is: " + id); } // OK printId(101); // OK printId("202"); // Error printId({ myID: 22342 }); type UnionType = number | {} | string | '123' | 2312 // number | {} | string

下面定义的两个type有很大的区别。

type Union = Array // (string | number)[] type Tuple = [string, number]

在这里元组长度是固定的,为2且第一个元素是string,第二个元素是number。 但是Union类型不限制长度。并且每个元素都既可能是string又可能是number。两个表达的含义就不一样了。

Union在conditional types中使用

经常会以 xxx extends UnionType的形式出现,ts会帮我们做是否包含的判断。例如

type Nullish = null | undefined type isNullish = T extends Nullish ? true : false type isNull = isNullish // true type isNull2 = isNullish // false // 我们可以再玩一些有意思的 type isNull3 = isNullish // 这里依然是true Union在mappd types中使用

Union经常会在mapped types中会用到。 首先下面的P的类型为"x" | "y"

type Point = { x: number; y: number }; type P = keyof Point; // "x" | "y" type Union = "x" | "y" type Obj = { [k in Union]: string } // { x: string; y: string };

因此下面五种type map之后都是一样的

type Union = "x" | "y" type Point = { x: number; y: number }; type Tuple = ["x", "y"] enum EnumMapKey { x, y, } enum EnumMapValue { First = 'x', Second = 'y', } // 第一种 type Obj1 = { [k in Union]: string } // 第二种 type Obj2 = { [k in keyof Point]: string } // 第三种 type Obj3 = { [k in Tuple[number]]: string } // 第四种 type Obj4 = { [k in keyof typeof EnumMapKey]: string } // 第五种 type Obj5 = { [k in `${EnumMapValue}`]: string } 或者是 type Obj5 = { [k in EnumMapValue]: string } // 效果是一样的。 Union在Template literal types中的使用

除了上面在将枚举中的value组成Union时用到Template literal types之外,它还可以和Union结合发挥强大力量。

type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type ThreeDigits = `${Digit}${Digit}${Digit}`; // "000" | "001" | "002" | "003" | "004" | "005" | "006" | "007" | "008" | "009" | "010" | "011" | "012" | "013" | "014" | "015" | "016" | "017" | "018" | "019" | "020" | "021" | "022" | ... 976 more ... | "999"

我们可以实现Union的排列组合了。不过值得注意的是转换之后的类型就转为了string,不再是number了。 如果你执行如下的操作,你会得到一个tserror。

type UniType = string | number | boolean | [number] type Template = `${UniType}` // Type 'UniType' is not assignable to type 'string | number | bigint | boolean | null | undefined'.

Union的成员必须得是简单类型。

小测试

假设我们有一个函数foo,接受两个参数,但是这个函数比较特殊,它要么接受一个string类型以及一个{name: string}类型,要么接受两个number类型的参数。那么如何定义这个函数类型。

foo('xx', {name: 'hello'}) // correct foo(3232, 232) // correct foo('xx', 123) // error!

一共有两种方案,答案如下

function foo(...args: [string, {name: string}] | [number, number]) { } foo('xx', {name: 'hello'}) foo(3232, 232) foo('xx', 123) // error! function bar(arg1: string, arg2: {name: string}): void function bar(arg1: number, arg2: number): void function bar(arg1: string | number, arg2: {name: string} | number) { } bar('xx', {name: 'hello'}) // correct bar(3232, 232) // correct bar('xx', 123) // error! // 很明显第一种简单。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有