Jacleklm's Blog

TypeScript基础语法小结

2020/04/14

初识 TypeScript

什么是 TS

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上

TS 的优点和缺点

优点:

  • 增加了代码的可读性和可维护性。写代码的时候有更多的编译提示,代码语义更清晰易懂
  • 包容性
  • 拥有活跃的社区

缺点:

  • 一定的学习成本;短期可能会增加一些开发成本,毕竟要多写一些类型的定义
  • 集成到构建流程需要一些工作量
  • 可能和一些库结合的不是很完美

详见 什么是 TS

基本使用方法

  • 编译代码:在命令行用 tsc <文件名> 的方式编译 .ts 文件,
  • 运行代码:然后用 node 执行生成的 js 文件
  • 安装了 ts-node 之后可以在命令行直接 ts-node <文件名> 的方式编译并执行 .ts 文件

静态类型的深度理解

  • 指定变量的类型
  • 变量会具有这个类型的属性和方法

常用语法

基础类型

个人理解:对象类型就是非基础类型,包括 函数,自定义类型,类 等

基础类型中,布尔值,数字,字符串基本和 JS 一样;数组有两种定义方式:

1
2
3
4
// 第一种(较常用)
let list: number[] = [1,2,3]
// 第二种
let list: Array<number> = [1,2,3]

元组 Tuple

一种数组,表示一个已知元素数量和类型的数组,各元素的类型不必相同。eg:

1
let list: [string, number] = ['Jacle', 23]

当访问一个越界的元素,会使用联合类型替代 (联合类型:联合类型表示一个值可以是几种类型之一。用 | 产生)

1
2
3
let list: [string, number] = ['Jacle', ’23’]
List[3] = 'Chen' // 可以执行,是string,number 的一种. // 其实还是会报错,说这个元组没有index3
list[4] = true // Error

枚举 enum

enum 类型是对 JavaScript 标准数据类型的一个补充。 像 C# 等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字

应用场景

普通 JS 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Status = { // eg. 处理一些后端code会用到这种写法
OFFLINE: 0,
ONLINE: 1,
DELETED: 2
}
function getResult(status) {
if (status === Status.OFFLINE) {
return 'offline';
} else if (status === Status.ONLINE) {
return 'online';
} else if (status === Status.DELETED) {
return 'deleted';
}
return status;
}
const result = getResult(Status.OFFLINE);
console.log(result);

应用枚举的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Status {
OFFLINE,
ONLINE,
DELETED
}
function getResult(status) {
if (status === Status.OFFLINE) {
return 'offline';
} else if (status === Status.ONLINE) {
return 'online';
} else if (status === Status.DELETED) {
return 'deleted';
}
return status;
}
const result = getResult(Status.OFFLINE);

枚举成员的初始化

  • 不对枚举成员的元素进行初始化的时候,默认其值是 0,1,2 … ;给某个成为初始化为某个数字都时候,后面的成员会默认接着这个数字进行初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Status {
OFFLINE,
ONLINE,
DELETED = 100,
TEST
}
console.log(Status.OFFLINE) // 0
console.log(typeof Status.OFFLINE) // number
console.log(Status[0]) // OFFLINE 反向查枚举成员的名称
console.log(typeof Status[0]) // string
console.log(Status.ONLINE) // 1
console.log(Status.DELETED) // 100
console.log(Status.TEST) // 101
  • 对某个成员初始化为非数字类型时,接下来的其他成员都得进行显式初始化,直到某个成员被显示初始化为数字类型
1
2
3
4
5
6
7
8
9
10
11
12
enum Status {
OFFLINE,
ONLINE = 'jacle',
DELETED = 'chen',
TEST = 10,
TETS2
}
console.log(Status.OFFLINE) // 0
console.log(Status.ONLINE) // jacle
console.log(Status.DELETED) // chen
console.log(Status.TEST) // 10
console.log(Status.TETS2) // 11

更详见 官方文档:枚举

any (常用)

我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量

1
2
3
let notSure: any = 4
notSure = 'maybe a string instead'
let list: any[] = [1, true, 'free']

void

void 类型像是与 any 类型相反,它表示没有任何类型。一般用于函数没返回值的时候使用;void 变量则只能赋值为 undefined 和 null

undefined 和 null (少用)

两者各自有自己的类型分别叫做 undefined 和 null。 和 void 相似,它们的本身的类型用处不是很大

never (少用)

never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。eg:

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

object

object 表示非原始类型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的类型

1
2
3
4
declare function create(o: object | null): void
create({ prop: 0 }) // OK
create(null) // OK
create(42) // Error

类型断言

使用类型断言的时候,TS 认为程序员已经很确定这个变量的类型了,会假设程序员已经做了必须的检查,常和 any 搭配使用。有两种写法:

1
2
3
4
5
6
7
8
// <>写法
let someVal: any = 'this is a string, I sure'
let strLength: number = (<string>someVal).length
console.log(strLength) // 24

// as写法 (似乎比较好记)
let someVal: any = 'this is a string, I sure'
let strLength: number = (someVal as string).length

类型注解(type annotation) & 类型推断 (type inference)

类型注解:明显写出来的,我们告诉 TS 变量是什么类型

类型推断:TS 自动的去尝试分析变量类型。当 TS 无法分析出来的时候,就需要我们写类型注解

变量声明

同 JS:var,let, const

类型别名(type alias)

就是 type 这种语法

1
2
3
4
5
6
7
8
9
type User = {
name: string,
age: number
}
let arr: User[] = [{
name: 'jacle',
age: 23
}]
type UserName = 'jacle' | 'jaclee'

个人理解:类型别名 和 接口 的区别不大,类型别名 能表示基础类型,接口 只能表示对象类型;能用接口的时候尽量用接口

接口

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约

个人理解是:接口是对一类数据结构的约定和描述

可选属性

可有可无的属性。eg:color?: string;

只读属性

只能在刚创建的时候赋值,后面不能修改,在接口的属性前加 readonly。eg:

1
2
3
4
5
6
interface Point {
readonly x: number,
readonly y: string
}
let point1: Point = { x: 2, y: 'test'}
point1.x = 3 // error

此外,TypeScript 的数组具有 ReadonlyArray<T> 类型,效果一样

1
let ro: ReadonlyArray<number> = [1, 2, 3]

总结:不能变的属性用 readonly,不能变的变量用 const

一个作用:接口可能带有任意数量的其它属性

如果我们创建一个接口,该接口除了我们明确约定好的属性,还可能会带有任意数量的其它属性,我们这时候就可以用 添加一个字符串索引签名

1
2
3
4
5
interface People {
age: number,
name: string,
[propName: string]: any,
}

但是其实这种情况还有一个不用索引类型的知识点(虽然不太严谨):直接传的对象有多余属性会报错,传的是变量的时候就不会

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Person {
name: string
age?: number
}
function getName(person: Person): void {
console.log(person.name)
}
let jacle = {
name: 'jacle',
school: 'SYSU'
}
getName({ // Error,school 不在 Person 中
name: 'jacle',
school: 'SYSU'
})
getName(jacle) // 正常

函数类型

接口也可以描述函数类型

1
2
3
4
5
6
interface searchFun {
(sour: string, data: number): boolean
}
let testFun: searchFun = function(sour: string, data: number): boolean {
return data > 0
}

类类型

接口也能用来描述类,强制一个类去满足某种契约。用 implements
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ClockInt {
currTime: Date,
setTime(d: Date)
}

class Clock implements ClockInt {
currTime: Date
setTime(d: Date) {
this.currTime = d
}
constructor() {
this.currTime = new Date()
}
}

接口的继承

一个接口可以继承一个或多个接口,创建出多个接口的合成接口。用 extends

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface class1 {
color: string
}
interface class2 {
age: number
}
interface class3 {
city: string[]
}
interface people extends class1, class2, class3 {
ishandsome: boolean
}
let jacle: people = {
color: 'blue',
age: 23,
city: ['beijing', 'guangzhou'],
ishandsome: true
}

可索引类型 && 索引签名

接口也可以描述那些能够“通过索引得到”的类型,比如数组或对象: a[10]ageMap['daniel']。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型(两部分)

一般情况下, 索引 和 索引签名 是一个概念;当我们声明一个索引签名的时候,就是声明了索引的类型,还有相应的索引返回值类型(两部分)

1
2
3
4
5
interface StrArr {
[index: number]: string // 暂时理解为这部分是索引签名。
}
let strA: StrArr = ['a', 'b', 'c']
let myStr: string = strA[0];

这个 index 只是发挥可读性的作用,你可以随便命名

索引签名

可看文档

JS中

  1. 在JS中,一般认为索引签名的类型是字符串和数字
    1
    2
    3
    4
    5
    6
    7
    let jacle = {
    name: 'jacle',
    age: 17
    }
    console.log(jacle['name']) // 索引签名是字符串
    let people = [jacle]
    console.log(people[0]) // 索引签名是数字
  2. 当我们把索引签名搞成对象时,JavaScript 会在得到结果之前会先调用 .toString 方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let obj = {
    toString() {
    console.log('toString called');
    return 'Hello';
    }
    };

    let foo: any = {};
    foo[obj] = 'World'; // toString called
    console.log(foo[obj]); // toString called, World
    console.log(foo['Hello']); // World

    只要索引位置使用了 obj,toString 方法都将会被调用

TS中

TypeScript 的索引签名必须是 string 或者 number。我们搞 obj 进去会报错的,强制用户必须明确的写出 toString()

声明一个索引签名

1
2
3
4
5
6
7
8
9
10
const foo: {
[index: string]: { message: string };
} = {};

// 储存的东西必须符合结构
// ok
foo['a'] = { message: 'some message' };

// Error, 必须包含 `message`
foo['a'] = { messages: 'some message' };

所有成员都必须符合字符串的索引签名

1
2
3
4
5
6
7
8
9
10
11
12
13
// ok
interface Foo {
[key: string]: number;
x: number;
y: number;
}

// Error
interface Bar {
[key: string]: number;
x: number;
y: string; // Error: y 属性必须为 number 类型
}

使用字符串字面量(字符串组成的联合类型)当索引签名

一个索引签名可以通过映射类型来使索引字符串为联合类型中的一员:

映射类型是 in、keyof、typeof 这些?

1
2
3
4
5
6
7
8
9
type index = 'a' | 'b' | 'c';
type fromIndex = {
[K in index]: number;
}
const good: FromIndex = { b: 1, c: 2 };
// Error:
// `{ b: 1, c: 2, d: 3 }` 不能分配给 'FromIndex'
// 对象字面量只能指定已知类型,'d' 不存在 'FromIndex' 类型上
const bad: FromIndex = { b: 1, c: 2, d: 3 };

同时拥有 string 和 number 类型的索引签名(少用)

TypeScript 可以同时使用字符串和数字两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型

1
2
3
4
5
6
7
interface ArrStr {
[key: string]: string | number; // 必须包括所用成员类型
[index: number]: string; // 字符串索引类型的子级

// example
length: number;
}

基本例子

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
greeting: string
constructor(message: string) {
this.greeting = message
}
greet() {
console.log('Hello, ' + this.greeting)
}
}
let greeter = new Greeter('world')
greeter.greet()

创建了一个 Greeter 类,有一个 greeting 属性,一个构造函数 constructor(所以构造函数中也得有 this.greeting)和一个 greet 方法

继承

同 ES6

此外,子类可以通过直接重写方法的形式重写父类的方法;重写的时候如果想调用父类的方法可以用 super

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
name = 'jacle'
getName() {
return this.name
}
}
class Teacher extends Person {
getName() {
return super.getName() + ' new'
}
}
let jacle = new Teacher()
console.log(jacle.getName()) // ‘jacle new'

公共,私有与受保护的修饰符

类的属性,静态属性,方法,构造函数 都可以加这些修饰符前缀,默认都是 public

  • 默认为 public。外部可访问
  • private。外部不可访问,只有内部(这个类自己)的方法可以访问
  • protected。只有 类自己 和 类的后代 可以访问
  • 也可加 readonly 修饰符,使得属性无法被后续修改

注意下面这两种写法都可以:

1
2
3
4
5
6
7
8
9
10
11
// 写法一
class Greeter {
protected greeting: string
constructor(message: string) {
this.greeting = message
}
// 写法二
class Greeter {
constructor(protected message: string) {
this.greeting = message
}

getter 和 setter

可以把属性写成 pravite 的,然后定义 getter 和 setter 进行属性的读改

用法:用 getset,一般会和 pravite 属性配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person3 {
constructor(private _name: string) { // 内部属性可以用 _ 进行区分,可以和 get 相配合
this._name = _name
}
get name() {
return this._name
}
set name(name: string) {
// 可以加额外的处理逻辑
this._name = name

}
getName() { // low 的写法
return this._name
}
}

let jacle2 = new Person3('jacle')
console.log(jacle2.name) // 用了 get 之后可以直接这么用
console.log(jacle2.getName())
jacle2.name = 'jack' // 调用了 setter

静态属性

属性加 static 前缀。静态属性存在于类本身上面而不是类的实例上,但是实例会用到这个属性,访问方法是 类.静态属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class ChenFamilyPeo {
static lastName = 'Chen'
public firstname: string
constructor(firstname: string) {
this.firstname = firstname
}
getFullName() {
return ChenFamilyPeo.lastName + ' ' + this.firstname
}
}
let jacle = new chenFamilyPeo('Junjia')
console.log(jacle.getFullName()) // Chen Junjia
console.log(ChenFamilyPeo.lastName) // Chen

应用举例:TS 中的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SingleObj {
private constructor(private name: string, private age: number) {
this.name = name
this.age = age
}
private static instance: SingleObj
static getInstance(name: string, age: number) {
if (!this.instance) {
this.instance = new SingleObj(name, age)
}
return this.instance
}
getAge() {
return this.age
}
}
let person1 = SingleObj.getInstance('jacle', 23)
console.log(person1.getAge()) // 23
let person2 = SingleObj.getInstance('jacle', 24)
console.log(person1 === person2) // true
console.log(person2.getAge()) // 23

抽象类 (少用)

抽象类做为其它派生类的基类使用(抽象子类 === 派生类)。 它们一般不会直接被实例化。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法
并且派生类一定要有抽象类中定义的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
abstract class Department {
name: string
constructor(name: string) {
this.name = name
}
printName(): void {
console.log('Department name: ' + this.name)
}
abstract printMeeting(): void // 必须在派生类中实现
}

class AccountingDepartment extends Department {
constructor(name: string) {
super(name) // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.')
}
generateReports(): void {
console.log('Generating accounting reports...')
}
}

let department: Department // 允许创建一个对抽象类型的引用
department = new Department() // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment('客增研发') // 允许对一个抽象子类进行实例化和赋值
console.log(department.name) // 客增研发
department.printName() // Department name: 客增研发
department.printMeeting() // The Accounting Department meets each Monday at 10am.
department.generateReports() // 错误: 方法在声明的抽象类中不存在

函数

函数类型

1
2
3
4
5
6
7
8
9
10
11
12
// 函数定义类型
function add(x: number, y:number): number {
return x + y
}
let add = function(x: number, y:number): number {
return x + y
}
//完整函数类型
let add: (x:number, y:number) => number =
function(x: number, y:number): number {
return x + y
}

可选参数 & 默认参数

  • TS 中参数是必须的,不能是 undifined,不能多也不能少。可以用 ? 表示可选参数
  • 默认参数同 ES6 如果位置靠前但是想启用默认参数,则传 undefined 即可

当我们想传好几个参数,但是参数不是必选的时候,有时候执行起来会不太优雅:

1
2
3
4
5
const testFn = (a?: number, b?: number, c?: number ) => {
console.log('a', a);
console.log('c', c);
}
testFn(1, undefined, 2);

可以改成这种对象的写法:

1
2
3
4
5
const testFn = (ops: { a?: number, b?: number, c?: number }) => {
console.log('a', ops.a);
console.log('c', ops.c);
}
testFn({ a: 1, c: 2});

函数重载

个人理解函数重载就是:函数根据传入不同的参数而返回不同类型的数据的场景 / 执行不同的逻辑。
在 TS 当中,除了在函数内部写判断类型并具体执行的逻辑之外,还要在函数之前 为同一个函数提供多个函数类型定义来进行函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
function pickCard(x: {suit: string; card: number }[]): number
function pickCard(x: number): {suit: string; card: number }

// function pickCard(x): any 并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象数组,另一个接收数字
function pickCard(x): any {
if (Array.isArray(x)) {
// 逻辑部分
return 1
} else if (typeof x === 'number') {
// 逻辑部分
return { suit: 'jack', card: 1 }
}
}

在定义重载的时候,一定要把最精确的定义放在最前面

泛型

泛型,泛指的类型

个人理解:泛型主要是用来提高数据类型方面的复用性:组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,比单纯写 string | number 等这种联合类型写法复用性更高
进一步理解:泛形应该理解为一个储存池,用来保存可能会用到的类型,相当于加了若干个类型参数。捕获输入类型只是泛型的常见应用

类型变量 T

语法是 <T>,可以帮助我们捕获用户传入的类型,eg:定义一个会返回任何传入它的值的函数

1
2
3
4
5
6
7
function identity<T>(arg: T): T { // 编译器会认为 T 是任意类型,所以该函数具有强的通用性
return arg
}
// 不好的写法:
function identity(arg: any): any { // 用any能有同样的效果,但是我们会丢失 参数类型 ,这个信息
return arg
}

我们把这个版本的 identity 函数叫做泛型,因为它可以适用于多个类型。可以使用泛型来创建可重用的组件
我们可以这么用该函数

1
let str = identity('jacle') // 编译器会根据传入的参数自动地帮助我们确定 T 的类型

泛型类型 & 接口 & 类

1
2
3
4
function rec<T>(arg: T): T {
return arg
}
let newre: <U>(arg: U) => U = rec

泛型接口

1
2
3
4
5
6
7
interface GenericIdentityFn<T> {
(arg: T): T
}
function identity<T>(arg: T): T {
return arg
}
let myIdentity: GenericIdentityFn<number> = identity

泛型类,与接口类似

1
2
3
4
5
6
7
8
9
function join<T>(a: T, b: T) {
return `${a}${b}`
}
join<string>('a', 2) // 函数使用的时候可以显式地生命泛型是什么类型,显然这里会报错;不显式则会被推断出来

function join<T, P>(a: T, b: P) {
return `${a}${b}`
}
join<string, number>('a', 2) // 泛型还可以定义多个,这里不会报错

泛型约束

泛型约束是一个很常用的用法,用来把类型约束成一个更严谨的类型 (使得泛型收窄),eg. 用来确保属性存在。其实泛型约束的方法挺多:extends这种简单的也算

1
2
3
4
function rec<T>(arg: T): T {
console.log(arg.length) // error。编译的时候认为arg是任何类型,所以不一定有length属性
return arg
}

所以我们可以定义一个接口做一些约束

1
2
3
4
5
6
7
8
9
interface lengthwise {
length: number
}
function rec<T extends lengthwise>(arg: T): T { // 意思是:T类型应该有length属性,并且该属性返回number
console.log(arg.length)
return arg
}
rec('jack')
rec(6) // error

keyof

keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。eg:

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number

所以有了 keyof 之后我们可以更愉快地和 泛型约束 玩耍

1
2
3
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

开发中还遇到不懂的泛型骚操作,详见 一文读懂 TypeScript 泛型及应用

条件类型

TypeScript中的类型编程

有时可发挥类似 interface 的作用

1
2
3
4
5
6
7
8
9
10
const FeatureContext = React.createContext<{
featureList: IFeature[];
supplementaryData: ISupplementaryData;
fetchLoading: boolean;
fetchFeatureData(): void;
searchFeature(queryKey: string): void;
verifyId: number;
}>(null);

const [fetchLoading, setFetchLoadingStatus] = useState<boolean>(true);

高级类型 (其实用的还挺多的)

交叉类型

交叉类型是将多个类型合并为一个类型。用 & 产生

联合类型

联合类型表示一个值可以是几种类型之一。用 | 产生

类型保护

通俗版

个人理解:类型保护一般出现在有联合类型的地方,类型保护就是用来判断变量类型的,并且在对应的 if 分支内能确定变量类型,进而能提前调用那种类型的函数 (比如提前判定是 string,然后在那个分支就可以用 .substr() )

类型保护的方式

  • 类型断言
  • in语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface Bird {
    fly: boolean
    sing: () => {}
    }
    interface Dog {
    fly: boolean
    bark: () => {}
    }
    // 类型断言的类型保护
    function trains1(animal: Bird | Dog) {
    if(animal.fly) {
    (animal as Bird).sing()
    } else {
    (animal as Dog).bark()
    }
    }
    // in 语法的类型保护
    function trains2(animal: Bird | Dog) {
    if('sing' in animal) {
    animal.sing()
    } else {
    animal.bark()
    }
    }
  • typeof
  • instanceof
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // typeof 和 instanceof 语法的类型保护
    function add(first: string | number, second: string | number) {
    if(typeof first === 'string' || typeof second === 'string') {
    return `${first}${second}`
    }
    return first + second
    }
    // instanceof 语法的类型保护 ,只用于能用 instanceof 的,eg. class (interface不能)
    class numberObj {
    count: number
    constructor(count: number) {
    this.count = count
    }
    }
    function add2(first: object | numberObj, second: object | numberObj) {
    // 这里不能写成 first instanceof numberObj === true
    if(first instanceof numberObj && second instanceof numberObj) {
    return first.count + second.count
    }
    return 0
    }

官方版

类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型

定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词:这里 pet is Fish 就是类型谓词

就是当下面这个函数返回true的时候,就会有 pet is Fish,在下面的 if 中相当于一种类型断言的作用了。所以这种函数类型其实也是一种类型保护,或者说是一种底层是类型断言的类型保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Fish {
constructor(public name: string) {
this.name = name
}
swim() {
console.log('I can swim, i am fish')
}
}
class Bird {
constructor(public name: string) {
this.name = name
}
fly() {
console.log('I can fly, i am bird')
}
}

const petUnknow = new Fish('pet')
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined
}
function trainAnimal(pet: Fish | Bird): void{
if(isFish(pet)) {
pet.swim() // 这里能这样调用而不会报错,因为这个分支认定 pet is Fish
} else {
pet.fly()
}
}
trainAnimal(petUnknow) // I can swim, i am fish

字符串字面量类型

字符串字面量类型允许你指定字符串必须具有的确切值.

字符串字面量 === 字符串组成的联合类型

1
2
3
// 你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
function getEase(ease: Easing) {}

Typescript 中的 Record, Partial, Readonly , Pick

TypeScript中的内置工具类型及其实现

命名空间

文档

工程化

单文件

使用方法。编译代码:在命令行用 tsc <文件名> 的方式编译 .ts 文件,然后用 node 执行生成的js文件

tsconfig.json 配置文件及其常用语法

在项目当中,根目录运行 tsc --init 能生成该项目的 TS 配置文件: tsconfig.json

运行方法

在根目录命令行单纯地执行 tsc ,会根据配置文件编译整个目录下所有的 .ts 文件(用ts-node ${fileName} 其实也会用这个配置文件) ;想只编译某些文件,可以 files / include 或 exculde (数组写法)eg.

1
2
3
4
5
{
"include": ["./single.ts"],
"compilerOptions": {

}

compilerOptions

官方文档

常用配置项:

  • 常规
    • removeComments: true 编译时去除注释
    • Incremental: true 上次编译过并且没变化的文件这次不会再编译
  • JS
    • allowJs: true 允许编译javascript文件(转成ES5)
    • checkJs: true 检查JS语法
  • 检查
    • noImplicitAny: true 当一个变量是 any 的时候,必须显示地写出来,不然会报错
    • strictNullChecks: true 在严格的 null检查模式下, null和 undefined值不包含在任何类型里,只允许用它们自己和 any来赋值
  • 输入输出
    • outDir: “./build”
    • rootDir: “./src” 此时src文件外不能有 .ts 文件
  • 额外检查
    • noUnusedLocals 若有未使用的局部变量则抛错
    • noUnusedParameters 同理,函数参数

  • 项目的script命令中写成 tsc -w ,这个 -w 能监控项目中 TS 文件的变化,有变化就自动重新编译

参考资料
TypeScript 入门教程
TypeScript 官方文档
慕课网:基于 TypeScript 从零重构 axios
慕课网:TypeScript-系统入门到项目实战

CATALOG
  1. 1. 初识 TypeScript
    1. 1.1. 什么是 TS
    2. 1.2. TS 的优点和缺点
    3. 1.3. 基本使用方法
    4. 1.4. 静态类型的深度理解
  2. 2. 常用语法
    1. 2.1. 基础类型
      1. 2.1.1. 元组 Tuple
      2. 2.1.2. 枚举 enum
        1. 2.1.2.1. 应用场景
        2. 2.1.2.2. 枚举成员的初始化
      3. 2.1.3. any (常用)
      4. 2.1.4. void
      5. 2.1.5. undefined 和 null (少用)
      6. 2.1.6. never (少用)
      7. 2.1.7. object
      8. 2.1.8. 类型断言
    2. 2.2. 类型注解(type annotation) & 类型推断 (type inference)
    3. 2.3. 变量声明
    4. 2.4. 类型别名(type alias)
    5. 2.5. 接口
      1. 2.5.1. 可选属性
      2. 2.5.2. 只读属性
        1. 2.5.2.1. 一个作用:接口可能带有任意数量的其它属性
      3. 2.5.3. 函数类型
      4. 2.5.4. 类类型
      5. 2.5.5. 接口的继承
    6. 2.6. 可索引类型 && 索引签名
      1. 2.6.1. 索引签名
      2. 2.6.2. JS中
      3. 2.6.3. TS中
        1. 2.6.3.1. 声明一个索引签名
        2. 2.6.3.2. 所有成员都必须符合字符串的索引签名
        3. 2.6.3.3. 使用字符串字面量(字符串组成的联合类型)当索引签名
        4. 2.6.3.4. 同时拥有 string 和 number 类型的索引签名(少用)
    7. 2.7.
      1. 2.7.1. 基本例子
      2. 2.7.2. 继承
      3. 2.7.3. 公共,私有与受保护的修饰符
      4. 2.7.4. getter 和 setter
      5. 2.7.5. 静态属性
        1. 2.7.5.1. 应用举例:TS 中的单例模式
      6. 2.7.6. 抽象类 (少用)
    8. 2.8. 函数
      1. 2.8.1. 函数类型
      2. 2.8.2. 可选参数 & 默认参数
      3. 2.8.3. 函数重载
    9. 2.9. 泛型
      1. 2.9.1. 类型变量 T
      2. 2.9.2. 泛型类型 & 接口 & 类
      3. 2.9.3. 泛型约束
        1. 2.9.3.1. keyof
        2. 2.9.3.2. 条件类型
        3. 2.9.3.3. 有时可发挥类似 interface 的作用
    10. 2.10. 高级类型 (其实用的还挺多的)
      1. 2.10.1. 交叉类型
      2. 2.10.2. 联合类型
      3. 2.10.3. 类型保护
        1. 2.10.3.1. 通俗版
        2. 2.10.3.2. 类型保护的方式
        3. 2.10.3.3. 官方版
      4. 2.10.4. 字符串字面量类型
    11. 2.11. Typescript 中的 Record, Partial, Readonly , Pick
    12. 2.12. 命名空间
  3. 3. 工程化
    1. 3.1. 单文件
    2. 3.2. tsconfig.json 配置文件及其常用语法
      1. 3.2.1. 运行方法
      2. 3.2.2. compilerOptions
    3. 3.3.