TS系列篇|基础数据类型 和 类型推论及断言

2,887 阅读9分钟

"不畏惧,不将就,未来的日子好好努力"——大家好!我是小芝麻😄

在开始之前先了解一个概念

类型注解:

  • 作用: 相当于强类型语言中的类型声明, 可以对变量起到约束作用
  • 语法: (变量、函数): type
  • 例子: let hello: string = 'hello'

一、基础数据类型

1、数字类型(number)

  • 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6; // 十进制
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制

2、字符串类型(string)

let name: string = '金色小芝麻'

3、布尔类型(boolean)

let isGirl: boolean = true

4、null 和 undefined

  • 默认情况下nullundefined是所有类型的子类型。
  • tonfig.json文件中--strictNullChecks设置为 true 标记(默认是 false)
    • nullundefined只能赋值给void和它们各自。
  • 反之,可以把 nullundefined赋值给number或者 string 等类型的变量。
let u: undefined = undefined; 
let n: null = null;

5、Symbol 类型

Symbol 是在ES2015之后成为新的原始类型, 所以在使用 Symbol 的时候,必须添加 es6 的编译辅助库, 如下:

"lib": ["es6", "dom"]

注意:如果不写 lib 时 TS 会自己默认识别编译, 但如果写了 lib 就必须指定所有需要使用的 辅助编译库, 否则就会导致编译不成功

let only: Symbol = Symbol(18)

6、BigInt 类型

  • BigInt 类型在 TypeScript3.2 版本被内置,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。
let res: BigInt = BigInt(Number.MAX_SAFE_INTEGER)

Symbol 情况类似,在使用 BigInt 的时候,必须添加 ESNext 的编译辅助库, 如下:

"lib": ["es6", "dom", "ESNext"]

7、数组类型(array)

let arr1: number[] = [1, 2, 3]
let arr2: Array<string> = ['1', '2', '3']

7.1 类数组类型

常用的类数组都有自己的接口定义: IArguments 、 HTMLElement 、 HTMLCollection 、 NodeListOf...

function sum(...arg: any[]) {
  // IArguments 是 TypeScript 中定义好了的类型
  let args: IArguments = arguments
  for (let i = 0; i < args.length; i++) {
    console.log(args[i])
  }
}
sum(1, 2, '3', 4)

8、元组类型(tuple)

  • 在 TypeScript 的基础类型中,元组(Tuple)表示一个已知数量类型的数组
let point: [number, number]
point = [100, 100] // 编译成功
point = [100, '10'] // 编译失败,因为 ‘10’ 不是 number 类型
let person: [string, number] = ['金色小芝麻', 18]
元组数组
每一项可以是不同的类型每一项都是同一类型
有预定义的长度没有长度限制
用于表示一个固定的结构用于表示一个列表

元组继承于数组,但是比数组拥有更严格的类型检查。

  • 元组越界问题:
    • TS 允许向元组中使用数组的push方法插入新元素:
    const point: [string, number] = ['0', 1]; 
    point.push(4);
    console.log(point); // 输出结果["0", 1, 4]
    
    
    • 但是访问新加入的元素时,会报错: image.png

9、枚举类型(enum)

枚举类型用于定义数值集合

  • 事先考虑某一个变量的所有可能的值,尽量用自然语言中的代词表示它的每一个值
  • 比如性别(男女)、月份(1-12)、星期(一-日)、颜色(红橙黄绿蓝靛紫)...

9.1 数字枚举

  • 当我们声明一个枚举类型时, 它们的值其实是默认从0开始依次累加的数字类型;
enum Direction { 
    Up, 
    Down, 
    Left, 
    Right 
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 0, 1, 2, 3
  • 当我们把第一个值赋值后,后面也会根据第一个值进行累加:
enum Direction { 
    Up = 1, 
    Down, 
    Left, 
    Right 
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 1, 2, 3, 4

9.2 枚举的本质

我们以下面的枚举为例:

enum Gender{
  GIRL,
  BOY
}
console.log(`李雷是${Gender.BOY}`); // 李雷是1
console.log(`韩梅梅是${Gender.GIRL}`); // 韩梅梅是0

编译后的 .js 文件如下:编译工具:https://www.typescriptlang.org/play

"use strict";
var Gender;
(function (Gender) {
    Gender[Gender["GIRL"] = 0] = "GIRL";
    Gender[Gender["BOY"] = 1] = "BOY";
})(Gender || (Gender = {}));
console.log("\u674E\u96F7\u662F" + Gender.BOY);
console.log("\u97E9\u6885\u6885\u662F" + Gender.GIRL);

相信大家能够很清晰的看出原理了,拆分理解

第一步 var Gender

  • 声明一个 Gender 变量 并且初始值 是 空对象

第二步 (function (Gender) {...})(Gender || (Gender = {}));

  • Gender 对象 作为自执行函数的 实参传入

第三步 Gender["GIRL"] = 0 :

  • Gender 对象 的 “GIRL” 属性赋值为 0
Gender = {
    "GIRL" : 0
}

第四步 Gender[Gender["GIRL"] = 0] = "GIRL" :

  • Gender 对象 新增一个 属性名为“0” 的属性,并且给其赋值为 "GIRL"
Gender = {
    "GIRL" : 0,
    "0": "GIRL"
}

以此类推...

所以最终的 Gender 对象 应该为:

Gender = {
    "GIRL" : 0,
    "BOY" : 1,
    "0" : "GIRL",
    "1" : "BOY"
}

这也是为什么 枚举 可以正反向同时映射的原因。

正向映射即:name => value, 反向映射即: name <=> value

9.3 字符串枚举

enum Direction { 
    Up = 'Up', 
    Down = 'Down', 
    Left = 'Left', 
    Right = 'Right' 
} 
console.log(Direction['Right'], Direction.Up); // Right Up

字符串枚举不支持反向映射

9.4 异构枚举

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

通常情况下我们很少会这样使用枚举,但是从技术的角度来说,它是可行的。

9.5 常数枚举

  • 在声明枚举前加一个 const 声明
const enum Colors {
  Red,
  Yellow,
  Blue,
}
console.log(Colors.Red, Colors.Yellow, Colors.Blue)

// 加了const 默认变为常数,不在生成对象,直接编译成
// console.log(0 /* Red */, 1 /* Yellow */, 2 /* Blue */);
  • 这样能避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问。

9.6 枚举成员的类型

枚举成员分为 常量枚举成员 const需要被计算的枚举成员 computed

  • 常量枚举成员:
    • 没有初始值的枚举成员
    • 对已有枚举成员的引用
    • 常量表达式
enum userInfo{
    a, // 没有初始值的枚举成员
    b = userInfo.a, // 对已有枚举成员的引用
    c = 10 + 1 // 常量表达式
}

常量枚举成员 会在编译时 就计算出结果,然后以常量的形式出现在 运行环境

  • 需要被计算的枚举成员:
    • 一些非常量的表达式
enum userInfo{
    d = Math.random(), 
    e = '123'.length
}

需要被计算的枚举成员 在编译时 结果被保留,在运行时才会被计算

在 computed 之后的枚举成员一定要赋值,否则会抛错

9.7 枚举类型与枚举成员的类型

在某些情况下,枚举和枚举成员都可以作为一种类型使用

  • 1、枚举成员没有任何初始值
  • 2、枚举成员都是数字类型
  • 3、枚举成员都是字符串类型(整个枚举不能作为类型使用,只能使用枚举成员作为类型) 两个枚举之间不可以进行比较
  • 枚举类型
enum A {a, b} // 枚举成员没有任何初始值
enum B {a = 1, b = 2} // 枚举成员都是数字类型
enum C {a = 'a', b = 'b'} // 枚举成员都是字符串类型

let age: A = 18 // 枚举可以作为类型使用
let age2: B = 18 // 枚举可以作为类型使用
age === age2 // ERROR 因为 两个枚举之间不可以进行比较
  • 枚举成员类型
enum A {a, b} 
enum B {a = 1, b = 2} 
enum C {a = 'a', b = 'b'} 

let a: A.a = 18 // 枚举成员可以作为类型使用
let b: A.b = 18 
let c: B.a = 18 
a === b // ERROR 相同枚举内的不同成员不可以进行比较
a === c // ERROR 因为 两个枚举之间不可以进行比较

9.8 枚举合并

  • 我们可以分开声明枚举,他们会自动合并
enum Direction { 
    Up = 'Up', 
    Down = 'Down', 
    Left = 'Left', 
    Right = 'Right' 
} 
enum Direction { 
    Center = 1 
}

编译为 JavaScript 后的代码如下:

var Direction; 
(function (Direction) { 
    Direction["Up"] = "Up";
    Direction["Down"] = "Down"; 
    Direction["Left"] = "Left"; 
    Direction["Right"] = "Right"; 
})(Direction || (Direction = {})); 
(function (Direction) { 
    Direction[Direction["Center"] = 1] = "Center"; 
})(Direction || (Direction = {}));

10、任意类型(any)

  • 声明为 any 的变量可以赋予任意类型的值。
let list: any[] = [1, true, "free"]; 
list[1] = 100;

11、unknown 类型

  • unknown 是 TypeScript 3.0 引入了新类型,是 any 类型对应的安全类型。
相同点let value: anylet value: unknown
value = true;OKOK
value = 1;OKOK
value = "Hello World";OKOK
value = Symbol("type");OKOK
value = {}OKOK
value = []OKOK
  • unknown 和 any 的主要区别是 unknown 类型会更加严格: 在对unknown类型的值执行大多数操作之前,我们必须进行某种形式的检查, 而在对 any 类型的值执行操作之前,我们不必进行任何检查。
  • 当 unknown 类型被确定是某个类型之前,它不能被进行任何操作 |不同点| let value: any | let value: unknown | |---|:---:| :---: | |value.foo.bar; | OK | ERROR |value();| OK | ERROR |new value();| OK | ERROR |value[0][1]; | OK | ERROR

12、void 类型

  • 某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,通常会见到其返回值类型是 void
function warnUser(): void { 
    console.log("This is my warning message"); 
}
  • 声明一个void类型的变量没有什么用,因为只能为它赋予undefinednull
let unusable: void = undefined;

13、never 类型

never 是其他类型的子类型,代表不会出现的值,没有类型可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

  • 在函数内部永远会抛出错误,导致函数无法正常结束
function createError(message: string): never {
  // 返回“从不”的函数不能具有可访问的终结点
  throw new Error('error');
}
  • 死循环,永远无法运行到的代码
function sum(): never {
  while (true) {
    console.log('hello');
  }
  // 上面的死循环,导致永远无法运行到 end point
  console.log('end point');
}

never 和 void 的区别

  • void 可以被赋值为 nullundefined 类型。never 则是一个不包含值的类型
  • 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止或会抛出异常

二、类型推论 和 类型断言

1、类型推论

指编程语言中能够自动推导值的类型的能力,它是一些强静态类型语言中出现的特性

  • 如果定义的时候就赋值就能利用到类型推断
  • 定义时未赋值就会推论成 any 类型

TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。如下面的例子

let x = 3;
x = '3' // ERROR

image.png 变量x的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

let username2
username2 = 18
username2 = '金色小芝麻'
username2 = null

2、类型断言 as

  • 类型断言可以将一个联合类型的变量,指定为一个更加具体的类型
  • 不能将联合类型断言为不存在的类型

2.1 语法

值 as 类型<类型>值

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型

2.1.1 值 as 类型

let name: string | number;
console.log((name as string).length);
console.log((name as number).toFixed(2));

2.1.1 <类型>值

let name: string | number;
let myName: number = (<string>name).length;

2.2 类型断言的限制

不能将联合类型断言为不存在的类型

let name: string | number;
console.log((name as boolean)); // ERROR

3、非空断言 !

"strictNullChecks": true 编译下: 在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。

interface Entity {
  name: string;
}
function processEntity(e?: Entity) {
  let s = e.name;  // ERROR
}

image.png 上例中:e 为可选参数,所以有可能不传,不传时 e 的值为 undefined , undefined接着调用 name 就会抛出异常

此时我们只需要加上 ! 就能避免错误

interface Entity {
  name: string;
}
function processEntity(e?: Entity) {
  let s = e!.name;  // 断言e是非空并访问name属性
}

参考文献

[1]. TypeScript官网

[2]. TypeScript中文网

[3]. TypeScript 入门教程