TypeScript——类型系统与类型推导

前言

TypeScript 是由 Microsoft 开发的一种开放源代码语言。 它是 JavaScript 的一个超集,这意味着你可以在 TypeScript 中使用 JS 已存在的所有语法,并且所有 JavaScript 脚本都可以当作 TypeScript 脚本,此外它还增加了一些自己的语法。TypeScript 对 JavaScript 添加的最的核心功能,就是它的类型系统。

1. 类型系统

在 TypeScript 中,可以使用类型提示来识别变量或参数的数据类型。 使用类型提示,可以描述对象的形状,这样不仅可以提供更好的文档,还能使 TypeScript 能够更好的验证代码是否正常工作。

通过静态类型检查,TypeScript 在开发初期就能发现代码问题,而 JavaScript 往往只能够在浏览器中运行时才会发现。 类型还让你可以描述代码的用途, 如果你是在团队中工作,那么在多人协作开发时也能更简单、高效。

类型还可以为开发工具提供各种便捷和生产力优势,例如 IntelliSense(智能感知)、基于符号的导航、转到定义、查找所有引用、语句结束和代码重构。

2. 类型声明

基本类型声明

TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明。

let foo: string

上面示例中,变量 foo 的后面使用冒号,声明了它的类型为 string

类型声明的写法,一律为在标识符后面添加 “冒号 + 类型” 。函数参数和返回值,也是这样来声明类型,下面是 TypeScript 中常用的声明类型的方式:

let foo: string

let age: number

let bar: boolean

let b: null

let c: undefined

let d: bigint

let e: symbol

function getVal(val: string): string {
    return val
}

同样的它们只能被赋值为该类型的值:

foo = '123'

age = 123

bar = true

b = null

c = undefined

d = 123456n

e = Symbol()

object 类型

根据 Javascript 的设计,object 可以作为所有对象、数组和函数的类型:

let a: object

a = {}
a = []
a = () => {}

上面示例中,对象、数组、函数都属于 object 类型。虽然 TypeScript 这样的行为比较贴合 JavaScript 作为弱类型语言的表现形式,但是在实际开发应用中却很少使用 object 作为以上三者的类型声明。对象、数组、函数的类型都有更为具体、规范的定义方式:

interface Person {
    name: string
}

type Fn = (args: any) => void

let a: Person

let b: any[]

let c: Fn

以上分别列举了最常见的用法,定义了对象、数组、函数的类型。而 interfacetype 的强大之处绝不仅仅于此,在后续的篇幅中我们会详细讲解这两个类型。

undefined 和 null 类型

值得注意的是,undefinednull 是所有类型的子类型,在非严格模式下undefinednull 这两种类型的变量可以赋值给其他类型,如下:
TypeScript——类型系统与类型推导_第1张图片

然而在严格模式下,上述的写法就会报错:

TypeScript——类型系统与类型推导_第2张图片

这里推荐开启 "strict": true 直接启用严格模式开发,可以更严格地检测类型错误,以减少错误的发生。

3. interface「接口」

interface “接口”,我们前端很熟悉的就是跟后端同学一起联调接口,但却不能和 TypeScript 中的 “接口” 混为一谈。

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。

如何描述对象的形状

TypeScript 中的接口是一个非常灵活的概念,除了可用于 对类的一部分行为进行抽象 以外,也常用于描述对象的形状

interface Person {
    name: string
    age: number
}

let p: Person

p = {
    name: '',
    age: 18
}

上述的代码就是 interface 的一个基本用法。我们都知道,在 JavaScript 中对象是可以随意的增加、删除、修改属性的,这样具备极大的灵活性,却也可能带来一定的开发风险。

而 TypeScript 只关心我们赋值给变量的 “值的结构”,它只关心它是否具有预期的属性。 只关心类型的结构和功能,这就是为什么 TypeScript 常常被称之为是一个 结构化类型 的类型系统。当编写以下代码后,TypeScript 会及时给出报错:

p.age = ''
// TS2322: Type  string  is not assignable to type  number 

p.gender = 'male'
// TS2339: Property  gender  does not exist on type  Person 

delete p.name
// TS2790: The operand of a  delete  operator must be optional.

灵活的 interface

那么可能有的同学可能就有疑惑了,我就是要这样写代码,TypeScript 这么不灵活,老子 TM 不学了 。其实不然,通过下述写法在保留可维护性、安全性的同时还能具备一定的灵活性:

interface Person {
    name?: string // 可选属性
    age: number | string // 联合类型
    [p: string]: any; // 任意属性
}

let p: Person

p = {
    name: '',
    age: 18
}

p.age = ''

p.gender = 'male'

delete p.name

我们可以在属性名后面加一个 ? 标记表示这个属性是可选的。这样在 初始化对象的时候 就可以根据需要选择是否添加某个属性,并且在后续操作中也可以 选择删除或增加 这个属性。

通过任意属性,我们可以很灵活的给一个对象扩展新的属性。在某些时候,对象属性值的类型可能会有多种,我们则可以用 联合类型 来定义。

4. type「类型别名」

我们已经学会在类型注解里直接使用对象类型和联合类型,这很方便,但有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。

这就是类型别名(type alias)。所谓类型别名,顾名思义,一个可以指代任意类型的名字。类型别名的语法:

type Name = string;

type NameResolver = () => string;

type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

也能用来定义对象和联合类型:

type Age = number

type Name = string

type Person = {
    name: Name
    age: Age
}

type Foo = Age | Name

跟定义 interface 很类似,将上述的示例代码改改就行:

type Age = number | string

type Person = {
    name?: string // 可选属性
    age: Age // 联合类型
    [p: string]: any; // 任意属性
}

let p: Person

p = {
    name: '',
    age: 18
}

p.age = ''

p.gender = 'male'

delete p.name

以上的代码可以直接运行,也是没有任何问题的。

5. typeof

JavaScript 中有一个操作符 typeof 可以用来获取一个值的类型,但是我们可能某些时候不会使用它,因为它不能准确分辨出对象和数组。在 TypeScript 中有一个同样的类型操作符 typeof ,它可以用来获取一个值 TS 类型,代码如下:

const info = {
    name: 'John',
}

const list = [1, 2, 3]

type Info = typeof info

type List = typeof list

类型 Info 此时等同于 {name: string}List 等同于 number[],我们来试一试加在变量上的表现:

TypeScript——类型系统与类型推导_第3张图片

通过使用 typeof 操作符可以获取任意值的 TS 类型,当某个复杂的对象已经写好了而我们又不太想手写它的类型时,typeof 就变得尤为有用:

const person = {
    name: 'lison',
    age: 18,
    id: 1,
    school: '清华大学',
    address: '北京市',
    birthday: '2001-01-01',
    height: 170,
    weight: 120,
    hobby: ['sleep', 'eat', 'run'],
    score: {
        math: 100,
        chinese: 90,
        english: 80
    }
}

function printPerson(p: typeof person) {
    console.log(p.name)
}

还有一个需要注意的点是,在 TypeScript 中,const 声明的变量具有字面量类型(literal types)。字面量类型是一种特殊的类型,用于表示一个固定的、不可变的值。这些类型包括字符串、数字、布尔值、对象、数组和元组等。当你使用 const 声明一个变量并给它赋一个具体的值时,TypeScript 会推断该变量的类型为字面量类型。例如:

const pi = 3.14159

type PI = typeof pi // 3.14159

当类型 PI 被作为某个变量的类型时,它就只能被赋值为 3.14159,否则就会报错:
TypeScript——类型系统与类型推导_第4张图片

6. 类型推导

如果没有明确的指定类型,那么 TypeScript 会依照类型推导(Type Inference)的规则推导出一个类型。

什么是类型推导

以下代码虽然没有指定类型,但是会在编译的时候报错:

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

事实上,它等价于:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

TypeScript 会在没有明确的指定类型的时候会通过解析代码推测出一个类型,这就是类型推导。正因为有这样的功能,在日常开发中,对于一些简单的、不会更改的数据我们可以不用显式去定义类型。

你可能感兴趣的:(TypeScript,typescript)