likes
comments
collection
share

Typescript学习(三) 字面量、联合类型、枚举类型

作者站长头像
站长
· 阅读数 16

字面量类型

前面我们在介绍元组的时候, 曾经提及一个类型: 1 | 2, 这种也是一个类型, 这看上去或许有些奇怪, 我们来看看以下案例:

const str = 'haha'
const num = 123
const bool = true
const obj = {}
const undefinedData = undefined
const nullData = null

可以试试, 这些变量的类型, 将被推导为它们各自的值, 比如, str的类型就是"haha", 这就是字面量类型, 字面量类型是所对应的原始类型的子类型;

// 成立, str为'haha'类型, 是string的子类型
const str = 'haha'
const str2: string = str

// 不成立, 理由两者字面量类型不同!
const str3: 'hehe' = str
// 不成立, 因为父类型不可赋给子类型!
const str4: 'heihei' = str2

当然, 字面量类型可不止一个字符串, 原始类型, 对象, 数组都有字面量类型!

const num = 100
const score:number = num // 正确
const age:18 = num // 报错, 字面量类型不同!

const bool = true
const isOk:boolean = bool
const isOlder:false = bool // 报错, 字面量类型不同!

关于字面量类型, 可以总结以下几点:

  1. 两个不同字面量类型的变量, 不得相互赋值;
  2. 字面量类型是原始类型的子类型; 所以字面量类型比原始类型更加精确;
  3. 字面量类型的值可以赋给原始类型的变量; 反之不行

但是, 想想实际开发中, 我们要一个字面量类型显然没什么意义, 无非就是规定这个变量只能赋值为某个固定的值, 那我还不如直接用const来声明并赋值得了, 这个Javascript就能做到呀, 还何必用到你Typescript呢? 所以字面量类型很多时候会和联合类型放在一起使用, 才能更好发挥出其真实作用;

联合类型

所谓联合类型, 就是几个类型的集合, 或者说是并集, 只要一个类型属于联合类型中的任意一个类型, 就可以认为, 这个类型是该联合类型的子类型! 联合类型通常用 | 符号来连接不同的类型; 前面说的字面量类型和联合类型配合起来, 可以限定取值范围:

interface Person {
  name: string;
  age: number;
  gender: 'male' | 'female' | '人妖'; // 人只有男/女/人妖
}

这样, 当我们使用这个类型的时候, 就会得到类型提示:

Typescript学习(三) 字面量、联合类型、枚举类型

注意, 这里的declare, 相当于只是在类型空间中声明了一个变量person, 在运行时这个变量是不存在的, 这里仅仅用于模拟出一个person变量, 方便观察它的类型特征, 而不用我们真的去实现一个Person类型的变量;

这种特定取值的用途还是挺广的, 比如接口返回的错误码, 产品规格这些有特定几个取值的场景;

当然联合类型也不是说只能是几个字面量类型, 它也可以接受原始类型, 几个类型混用, 来适应更多的场景

type more = 'str' | 123 | (() => {}) | (12 | 13) | 1 | 2 | [] | {name:string}

我们用到了type, 即 一个类型别名, 通常用来声明一个类型, 可以和interface混用, 这里我们声明了一个more的联合类型;

这里有几个点要注意:

  1. 函数没有字面量类型, 所以只能用(() => {})来表达;
  2. 联合类型中的联合类型将被拆开, 放进整个大的联合类型中, 例如: (12 | 13), 被混入后, 就是12 | 13 两个类型, 12 | 13 不再是一个单独完整的类型
let test:more = (12 | 13) // 报错
let test2:more = 12 // 正确

联合类型中, 如果我们联合几个对象字面量类型, 且这几个对象字面量类型又有相同的属性; 我们可以利用相同的属性来进行判断, 从而又能利用Typescript的类型推导来让我们的代码更加严谨!

Typescript学习(三) 字面量、联合类型、枚举类型

在本案例中, 所有对象字面量类型都有isLikeFish, 且isLikeFish为true的只能是猫, 所以, 在这个判断分支下, name必然是"cat"; 而如果我们再增加一个类型, 也喜欢鱼, 那么, 该判断分支又会自动推导为一个字面量联合类型

Typescript学习(三) 字面量、联合类型、枚举类型

总结:

  1. 字面量类型更加精确, 但是, 切记它们只是类型, 而不是值!
  2. 字面量类型经常和联合类型一起使用

枚举

和对象的对比

我们在Javascript中, 如果有几个固定的常量, 我们通常使用一个对象来管理, 这样, 一方面在写代码的时候可以得到输入提示, 另一方面也可以将这些常量纳入统一配置, 如果将来其中某些常量改变了, 我们只需要修改配置即可:

// 比如,商品宣传文案:
const GOODS = {
  FOOD: 'delicious food',
  CLOTHES: 'pretty clothes',
  SHOE: 'fashionable shoe',
}

我们可以在需要用到的地方直接使用GOODS.FOOD的方式来表示这个变量, 而不是每个地方都写死一段固定的文案;

而在Typescript中, 枚举就可以更好地适应这个场景:

enum GOODS {
  FOOD = 'delicious food',
  CLOTHES = 'pretty clothes',
  SHOES = 'fashionable shoes'
}

看看编译后的代码:

"use strict";
var GOODS;
(function (GOODS) {
    GOODS["FOOD"] = "delicious food";
    GOODS["CLOTHES"] = "pretty clothes";
    GOODS["SHOES"] = "fashionable shoes";
})(GOODS || (GOODS = {}));

相对于使用对象来讲, 枚举的好处在于:

  1. 对象的方式, 属性可以被再次修改, 即使你用const也没用, 一样会被改掉属性的值 GOODS.FOOD = 'bad food' 仍然是成立的;
  2. 常量枚举, 只需在前面加上const, 就可以使编译后的代码, 只保留使用了的那部分, 而未使用的部分会被忽略;
const enum GOODS {
  FOOD = 'delicious food',
  CLOTHES = 'pretty clothes',
  SHOES = 'fashionable shoes'
}
console.log('my shoes is' + GOODS.SHOES)

编译后, 代码将十分简单:

"use strict";
console.log('my shoes is' + "fashionable shoes" /* GOODS.SHOES */);
  1. 这些常量都被约束在了一个命名空间之下

分类

前面介绍的枚举, 我们可以称之为字符串型枚举; 除此之外, 枚举还有一些其他类型的枚举:

  1. 数字枚举, 即 在没有给枚举设置值的时候, 其默认为数字, 并从0开始递增
enum nums {
  one,
  two,
  three
}
nums.one // 0
  • 对象都是键到值单向映射, 而数字型枚举则是既可以键到值, 又可以从值到键, 实现双向映射, 上面的案例中, 我们同样可以通过值(0), 来访问枚举的键(one)
console.log(nums[0]) // one

其原理也很简单, 看看编译后的代码:

"use strict";
var nums;
(function (nums) {
    nums[nums["one"] = 0] = "one";
    nums[nums["two"] = 1] = "two";
    nums[nums["three"] = 2] = "three";
})(nums || (nums = {}));
  • 如果我们给某个枚举成员赋予一个新的数字, 那么其后的成员会以此为基础递增
enum nums {
  one,
  two = 999,
  three
}
console.log(nums.three) // 1000
  • 延迟求值

有时候, 枚举的某个成员的值是通过执行一个方法得出的

const sum = () => 12 + 12
enum nums {
  one,
  two = sum(),
  newMember = 1,
  three
}

这里加了一个newMember成员, 并初始化了一个值, 注意, 一定要有这个成员存在, 否则three就会报错(Enum member must have initializer)! 这是因为, 延迟求值的成员, 其后不得直接跟一个未赋值的枚举成员, 因为延迟求值成员的出现, 打断了原有的递增规律, 所以需要在此之后, 就必须明确地指出下一个成员的值, 哪怕这个值, 和其他成员的值相同, 也行!

// 延迟求值
const sum = () => 12 + 12
enum nums {
  one,
  two = sum(),
  three = 0 // one的值也是0, 此处重复了, 也允许
}
console.log(nums[0]) // three, 以最后一个为准

console.log(nums.one) // 0
console.log(nums.three) // 0
  1. 异构枚举

所谓的异构枚举, 其实就是字符串枚举和数字枚举的混合体, 这个很好理解, 唯一需要注意的, 还是和延迟求值枚举一样的问题:

enum nums {
  one,
  two,
  newMember = 'jack',
  three // 这行会报错, 要么给它初始化一个值, 要么在它前面再插一个初始化了的值!
}

异构枚举实际开发中一般使用较少, 但也需要了解

枚举小节:

  1. 枚举的部分作用类似于Javascript中, 管理常量的对象, 但是与之相比, 更加严谨, 代码更加健壮;
  2. 数字枚举的值从0开始依次递增; 数字枚举可以双向映射;
  3. 当数字枚举的递增规律被存在非数字的值的成员'打断'时, 其后的首个成员必须初始化一个值; 例如: 延迟求值成员和字符串成员后的首个成员, 都必须被初始化;