TypeScript系列 --- 基础类型

254 阅读9分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

安装

# 安装typescript后会存在一个指令 tsc (typescript compiler)来帮助我们将TS转换为JS
# 全局安装是为了tsc成为命令行全局指令,可以根据项目需求局部安装使用
npm install typescript -g

使用

可以通过命令行工具tsc,或使用相应插件(如webpack中的ts-loader)将ts编译功能集成到构建工具中来使用TypeScript

# 编译后,会在源文件同级目录下生成 和源文件同名的js文件
tsc <源文件名或文件夹名>

# 只进行类型校验,但是不生成编译后的js文件
tsc <源文件名或文件夹名> --noEmit

# 实时编译监听
tsc <源文件名或文件夹名> --watch

类型标注

TypeScript最为核心的功能就是为JavaScript添加上了类型系统

TypeScript是通过类型标注的形式来检测我们的数据类型

所以TypeScript的类型检测是静态的,在编译阶段执行的

// 类型: 类型注解 = 值
let ts: string = 'typeScript'

注意:类型注解是区分大小写的,numberNumber不是一种数据类型

// baz是字符串类型
let baz: string = 'typeScript'

// bar是字符串包装类类型
let bar: String = 'typeScript'

数据类型

程序最为基本的处理单元就是数据类型:数字,字符串,结构体,布尔值等

TypeScript支持与JavaScript几乎相同的数据类型,并在此基础上进行了相应的扩展

boolean

和在JavaScript中一样, TypeScript中的boolean类型的值,也只有true/false

let isDone: boolean = false;

number

和JavaScript一样,TypeScript里的所有数字都是浮点数(number)或者大整数(bigInt)

TypeScript中的number支持十进制八进制, 二进制十六进制

// 进制标识符不区分大小写 0b1010 <=> 0B1010, 0xf00d <=> 0Xf00d, ...
let decLiteral: number = 6; // 十进制
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制

// bigInt的n是区分大小写的
// bigInt的类型是 bigint ==> 都是小写的
let bigLiteral: bigint = 100n;
let foo: bigint = 100N; // => error

string

和JavaScript一样,可以使用双引号(")或单引号(')以及模板字符串表示字符串。

let firstname: string = 'Klaus'
let lastname: string = 'Wang'
let fullname: string = `${firstname} - ${lastname}`

array

TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。

// 第一种,可以在元素类型后面接上[],表示由此类型元素组成的一个数组
let list: number[] = [1, 2, 3];
// 第二种方式是使用数组泛型,Array<元素类型>
let list: Array<number> = [1, 2, 3];

tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,可以认为元组是一种特殊类型的数组

元组主要有以下两个特点:

  1. 元组的长度是固定的
  2. 元组中各个位置上的数据项的数据类型都是确定的(可以不必相同)
// Declare a tuple type
let x: [string, number];

// Initialize it
x = ['hello', 10]; // OK

// Initialize it incorrectly
x = [10, 'hello']; // Error

// 当访问一个已知索引的元素,会得到正确的类型
console.log(x[0].substr(1));

// 当访问一个越界的元素会报错。
x[3] = "world";
console.log(x[5].toString());

enum

enum类型是对JavaScript标准数据类型的一个补充。

使用枚举类型主要是用来为一组数值赋予友好的名字。

enum Color {Red, Green, Blue}

// 默认情况下,从0开始为元素编号。
let c: Color = Color.Green; // 等价于 let c = 1
// 可以手动的指定成员的数值
// 赋予的值必须是字符串类型或数值类型
enum Color {Red, Green = 2, Blue} // { Red: 0, Green: 2, Blue: 3 }

// 或者,全部都采用手动赋值
enum Color {Red = 1, Green = 2, Blue = 4}
// 既可以通过别名来获取枚举值,也可以通过枚举值来获取别名
enum Color {Red = 1, Green, Blue}

console.log(Color.Green) // => 2
console.log(Color[2]) // => 'Green'

any

  1. 在编程阶段还不清楚变量的数据类型
  2. 不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查
// any => 不进行类型检测,就相当于一个占位符
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; 

在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。

// 就像在其它语言中那样, object有着和any相似的作用, 但是他们之间有着本质区别

let notSure: any = 4;
// 设置了any,跳过类型检测,所以可以调用任何的方法,无论这个方法是否存在
notSure.ifItExists(); // success
notSure.toFixed(); // success

let prettySure: Object = 4;
// 设置了object, typescript会将其认作顶级对象类型object
// 此时该变量只能调用所有对象上通用的方法,即在object原型中定义的方法
// 其余的方法皆无法调用,即便它真的有这些方法
prettySure.toString(); // success
prettySure.toFixed(); // error

注意:

  1. 应避免使用object,而是使用非原始object类型
  2. 应避免使用any,而是使用unknown类型

当你只知道一部分数据的类型时,any类型也是有用的。

// 比如,你有一个数组,它包含了不同的类型的数据
let list: any[] = [1, true, "free"];

list[1] = 100;

unknown

  1. 在编程阶段还不清楚变量的数据类型
  2. 想要让编译器以及未来的用户知道这个变量可以是任意类型
  3. 不希望这个变量绕过类型检测器的检测
let notSure: unknown = 4;
notSure = "maybe a string instead";
notSure = false;

这么看,unknown和any似乎是一致的,但是他们存在这本质区别

let any: any
let unknown: unknown

let num: number
let str: string

// any即是最顶层类型,又是最底层类型 --- any就是任何没有类型限制
// 可以将any赋值给任何的数据类型
num = any
str = any

// unknown是最顶层类型,但不是最底层类型 --- unknown表示不确定,但不希望没有类型检测
// 不可以将unknown赋值给任何的数据类型
num = unknown // error
str = unknown // error

// unknown和any都是最顶层类型,可以相互赋值
unknown = any // success
any = unknown // success => unknown 只能被赋值给unknown类型的值 和 any类型的值

如果有一个 unknwon 类型的变量,你可以通过进行 typeof 、比较或者更高级的类型检查来将其的类型范围缩小,

使unknown转换为一个更为具体的类型

const maybe: unknown;

if (maybe === true) { // maybe的类型 从unknown被缩小为boolan
  const aBoolean: boolean = maybe; // success
  const aString: string = maybe; // error
}

if (typeof maybe === "string") { // maybe的类型 从unknown被缩小为string
  const aString: string = maybe; // success
  const aBoolean: boolean = maybe; // error
}

null 和 undefined

  1. undefinednull两者各自有自己的类型分别叫做undefinednull

  2. void相似,它们的本身的类型用处不是很大

  3. 默认情况下nullundefined是所有类型的子类型。

    也就是说你可以把nullundefined赋值给任意其它任意类型的值

let u: undefined = undefined;
let n: null = null;

注意:在实际开发中,推荐尽可能地开启--strictNullChecks`标记

而当指定了--strictNullChecks标记,nullundefined只能赋值给any和它们各自的类型,这能避免_很多_常见的问题(当然有一个例外是undefined可以赋值给void类型)

也许在某处你想传入一个stringnullundefined,你可以使用联合类型string | null | undefined

void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型

// 当一个函数没有返回值时,你通常会见到其返回值类型是void

// 在JavaScript中,函数没有返回任何值的时候,返回值是undefined,
// 所以可以认为,在typescript中 undefined是void的子类型
function warnUser(): void {
  console.log("This is my warning message");
}

可以声明一个void类型的变量,但是这么做通常没有什么大用,因为你只能为它赋予null(只在--strictNullChecks未指定时)和undefined

let unusable: void = undefined;

never

  1. never类型表示的是那些永不存在的值的类型
  2. never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
  3. never类型是任何类型的子类型,也可以赋值给任何类型;
  4. 没有类型是never的子类型或可以赋值给never类型(除了never本身之外)
  5. 即使any也不可以赋值给never
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

object

object表示非原始类型,也就是除numberstringbooleanbigintsymbolnullundefined之外的类型

在多数情况下,我们推荐使用接口等更为具体的形式来定义对象,因为这样可以更为准确的确定对象中各个属性的数据类型

// 直接定义更为具体的类型
let user: {
  name: string,
  age: number
}

// 或者使用接口
interface IUser {
  name: string,
  age: number
}

let user: IUser

但是在一些只需要使用对象作为参数,并不关心对象的属性值的类型的时候(例如:定义Object.create这类方法的时候)

我们可以使用object作为类型注解

declare function create(o: object | null): void;

create({ prop: 0 }); // success
create(null); // success

create(42); // error
create("string"); // error

类型断言

有时候我们会比TypeScript更了解某个值的详细信息,更为清楚地知道一个实体具有比它现有类型更确切的类型。

类型断言好比其它语言里的强制类型转换,只不过类型断言没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

另一个为as语法:

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

两种形式是等价的。但是实际使用的时候,更为推荐使用as进行类型断言。因为当你在TypeScript里使用JSX时,只有as语法断言是被允许的。

function getLength(param: number | string) {
 return param.length ? param.length : param.toString().length // error
}

但是此时,我们知道我们编写的函数是没有任何问题,所以为了解决TypeScript类型检测器的错误提示,

此时我们可以使用类型断言 (当前我们也可以使用类型守卫来解决,这里以类型断言为例)

function getLength(param: number | string) {
 return (param as string).length ? (param as string).length : param.toString().length
}

通常TypeSCript编译器认为,只有有关联的两个类型之间,才可以使用断言,否则类型断言会失败

const num: number = 123
const str: string = num as string // error 类型断言失败

如果的确需要转换,可以先将数据类型转换为any或unknown,在转换为具体的类型

除非迫不得已,否则这种方式是不被推荐的

const num: number = 123
const str: string = num as unknown as string // success