说明

本文结构全部采用无序列表,是对 TS 基础知识的概览,发现这种结构阅读起来更顺畅,比那种分层次的减少了一层大脑加工。

正文

  • 为什么使用 TypeScript
    • 前提:JavaScript 是一门动态类型语言,许多低级错误(类型错误)无法在开发阶段发现,为了减少 bug 出现几率,需要给 JavaScript 加入类型定义方便在编写代码阶段发现错误。
  • TypeScript 是什么?
    • TypeScript 是 JavaScript 的超集,可以理解为加入了类型定义的 JavaScript 语言一个分支。
  • 安装:
1
2
yarn global add typescript
// 安装完毕后内置了tsc命令(typescript compiler)
  • 手动编译
1
2
3
4
5
6
// hello.ts
const fun = (name: string) => {
console.log(name)
}
// 编译成js文件
tsc hello.ts
  • 一步到位自动编译并执行
1
2
yarn global add ts-node
ts-node class.ts
  • 原始类型
    • number、string、null、undefined、boolean、BigInt、Symbol
  • 定义原始类型
    • 注意:null 和 undefined 是所有类型的子类型
  • 定义联合类型
1
2
3
let numberOrString: string | number = 123
numberOrString = 'abc'
numberOrString = false 🚨
  • 数组和元组
1
2
3
4
// * 定义数组的方法一
let arr: number[] = [1, 2, 3]
// * 元组是限定了数据类型和数据长度的数组
let user: [stirng, number] = ["abc", 123] // 数据类型对应,长度对应,不能多也不能少
  • 对象/类

    • 定义对象/类需要使用到interface接口
    • interface接口
      • 两个常见用途:1、对对象的形状进行描述;2、对类的属性进行描述;
  • 约束对象的形状(shape)

    • 必须严格对应
1
2
3
4
5
6
7
8
9
10
interface Person {
readonly id: number
name: string
age?: number
}

let orime: Person = {
name: "orime",
age: 13,
}
  • 函数类型定义的两种写法

    1. 函数声明写法
    2. 函数表达式写法
  • 函数声明

1
2
3
4
5
6
7
function sum(x: number, y: number, z: number = 10): number {
if (x) {
return x + y + z
} else {
return x + y
}
}
  • 函数表达式
1
const sum2: (x: number, y: number, z: number = 19) => number = sum
  • 类型推断
1
2
let str = 'abc'
str = 123 🚨
  • 类的作用
    • 可以理解为一切实例的抽象模板

Tips:子类中使用 super 指向父类引用
image.png

  • 类属性修饰符

    • public:默认,实例或子类中都可以访问/修改
    • readonly:只读,只能访问不能修改
    • private:只能在定义类的代码块内访问(子类/实例都不能访问)
    • protect:只有子类和自己能访问(实例不能访问)
    • static:静态属性,只能类对象自己调用(当这个属性/方法和实例/子类没有太大关系的时候可以使用)
  • implement

    • 当两个类有公共属性的时候,如果用子类继承父类的方式显得不太灵活
    • 这时候可以将公共属性使用 interface 进行定义,再用 implement 进行实现
      • 实现多个接口用逗号分隔
      • 接口还能进行合并,比如 interface Battery extend Radio { ... }
  • Enum 枚举类型

    • 使用enum关键字进行定义
    • 定义好了还能直接取值
      • 直接取是取索引,从 0 开始
      • 使用索引取取到的是字符串
1
2
3
4
5
6
7
8
9
10
11
enum Direction {
Up = 10, // *
Down,
Left,
Right,
}

console.log(Direction.Up) // 10
console.log(Direction[12]) // Left

module.exports = {}
  • enum 常见用法

image.png

  • 不加常量枚举的编译结果

image.png

  • 加常量枚举

image.png

  • 泛型并不难,耐心看很简单
1
2
3
4
5
function echo<T>(arg: T): T {
return T
}

const res = echo("abc") // res -> string 类型
  • 泛型就像是个形参变量(占位符),实参是类型,使用的接收手动传入的或自动推导出的类型值
1
2
3
4
5
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}

const result = swap(["abc", 123]) // result -> [number, string]
  • 泛型约束
    • 单一泛型描述的属性不够用的时候,可以使用extends关键字扩展该泛型的属性
1
2
3
4
5
6
7
8
9
10
interface IWithLength {
length: number
}

function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}

const res = echoWithLength("abc") // 传入只要有length属性的值都可以

image.png

  • 使用泛型约束类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Queue<T> {
private data = []
push(item: T) {
this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}

// * 约束了传入和弹出的item类型一致,相比any可获得更好的类型提示
const queue1 = new Queue<number>()

queue1.push(1)
queue1.push(3)
console.log(queue1.pop().toFixed(2))
  • 泛型和接口interface一起使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface KeyPair<T, U> {
key: T
value: U
}

const obj1: KeyPair<string, number> = {
key: "abc",
value: 13,
}

const obj2: KeyPair<number, string> = {
key: 12,
value: "34",
}

console.log(obj1, obj2)

export {}
  • 定义数组的方式二:使用泛型
1
2
let arr: Array<number> = [1, 2, 3]
let arr1: number[] = [1, 2, 3]
  • 使用泛型修饰一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IPlus<T> {
(a: T, b: T): T
}
function plus(a: number, b: number): number {
return a + b
}
function connect(a: string, b: string): string {
return a + b
}

const plus1: IPlus<number> = plus
const connect1: IPlus<string> = connect

console.log(plus1(1, 2), connect1("a", "b")) // 3,ab

export {}
  • 类型别名(type aliases)
    • 在联合类型上应用较多
1
2
3
4
5
6
7
8
9
type NameResolver = () => string
type NameOrResolver = string | NameOrResolver
function getName(n: NameOrResolver) {
if (typeof n === "string") {
return n
} else {
return n()
}
}
  • 类型断言(type assertion)告诉编译器你比他更了解当前数据类型,并要求其不再为此而报错
    • 类型断言只能断言为联合类型中已存在的一种类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getLength(str: string | number): number {
// * 主流写法
// const s = str as String // ! 必须大写为 String
// if(s.length) return s.length
// else {
// const n = str as Number // ! 必须大写为 Number
// return n.toString().length
// }
// * 另一种写法 -> 不推荐
if ((<string>str).length) {
return (<string>str).length
} else {
return (<number>str).toString().length
}
}

console.log(getLength(123)) // 3

export {}
  • 第三方库的声明文件
    • 参考官方文档
    • 项目中的声明文件一般是以*.d.ts形式放到@types文件夹或另外指定的文件夹下
  • 定义声明文件的方式一:
    • declare var 声明全局变量
    • declare function 声明全局方法
    • declare class 声明全局类
    • declare enum 声明全局枚举类型
    • declare namespace 声明(含有子属性的)全局对象
    • interface 和 type 声明全局类型
1
2
// JQuery.d.ts
declear const JQuery: (selector: string) => void
  • 定义声明文件的方式二
    • 比如 jQuery 是一个全局变量,它是一个对象,提供了一个 jQuery.ajax 方法可以调用,那么我们就应该使用 declare namespace jQuery 来声明这个拥有多个子属性的全局变量。
1
2
3
4
5
6
7
8
// src/jQuery.d.ts

declare namespace jQuery {
function ajax(url: string, settings?: any): void;
}
// src/index.ts

jQuery.ajax('/api/get_something');
  • 注意,在 declare namespace 内部,我们直接使用 function ajax 来声明函数,而不是使用 declare function ajax。类似的,也可以使用 const, class, enum 等语句9:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/jQuery.d.ts

declare namespace jQuery {
function ajax(url: string, settings?: any): void;
const version: number;
class Event {
blur(eventType: EventType): void
}
enum EventType {
CustomClick
}
}
// src/index.ts

jQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);