TypeScript学习笔记

TypeScript学习笔记

小叶子

封面作者:みなはむ

请先阅读JavaScript学习笔记

简介

TypeScriptJavaScript 的一个超集, 支持静态类型检查, 可以在编译时发现并纠正错误, 由微软开发并维护; 在运行阶段, TypeScript 代码会被编译成 JavaScript 代码

TypeScript 有类型推断机制, 因此现有的 JavaScript 代码只需少量修改即可迁移到 TypeScript 中 (如果不是严格模式, 甚至可能不需要修改)

1
2
3
4
5
6
# 安装
pnpm add typescript -D
# 编译指定文件
pnpx tsc index.ts
# 运行编译后的代码
node index.js
1
2
3
# Deno、Bun 等现代环境内置了 TypeScript 支持
deno run index.ts
bun index.ts

tsconfig.json

TypeScript 使用 tsconfig.json 文件来配置编译选项, 可以使用 pnpx tsc --init 命令生成默认配置文件

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
/* 这是 Vite (React) 生成的 tsconfig.json 文件 */
{
"compilerOptions": {
"target": "ESNext", // 编译后的目标版本
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"], // 编译时需要引入的库
"module": "ESNext", // 模块规范
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

类型

TypeScript 可以使用 : 来定义变量的类型

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
31
32
33
34
35
36
37
// 基本类型
const any: any = 1 // ts 的新增类型, 可以接受任意类型的值
const num: number = 1
const str: string = 'hello'
const bool: boolean = true
const nul: null = null
const und: undefined = undefined
const void: void = undefined
const sym: symbol = Symbol('symbol')
const big: bigint = 100n

// 复杂类型和函数
const arr: number[] = [1, 2, 3] // 或 : Array<number>
const tuple: [string, number] = ['leaf', 18] // 元组类型
const obj: { name: string, age: number } = { name: 'leaf', age: 18 }
const funExp: (a: number, b: number) => number = (a, b) => a + b
const voidFun: () => void = () => console.log('void')
function fun(a: number, b: number): number { return a + b }

// 枚举类型
const result: 'success' | 'error' = 'success'
// enum 关键字
enum Color { R = 'red', G = 'green', B = 'blue', Y = 'yellow' }
const favColor: Color = Color.Y
// 注意: 枚举类型会在编译后被转换成对象, 除非使用常量枚举
const enum Direction { Up, Down, Left, Right }
const dir: Direction = Direction.Up
// 常量枚举在编译后会被移除, 只保留值

// Promise
const promise: Promise<number> = new Promise((resolve, reject) => {
resolve(1)
})

// 类型推断
let a = 1 // 推断为 number 类型
let b // 推断为 any 类型, 严格模式下会报错

unknown

unknown 类型是 any 类型的安全版本, 可以接受任意类型的值, 但不能直接操作, 需要先进行类型检查

1
2
3
4
5
6
let value: unknown
value = 1
// value += 1 // 报错: 对象的类型为 "unknown"。ts(2571)
if (typeof value === 'number') {
value += 1
}

never

never 类型表示永远不会返回结果的类型, 通常用于抛出异常或无限循环

1
2
3
4
5
6
function throwError(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}

类型断言

TypeScript 可以使用 as 关键字来断言变量的类型; 断言只在编译阶段起作用, 不会影响运行时的类型

1
2
3
4
5
6
7
8
9
const arr: number[] = [0, 1, 2, 3]
const num = arr.find(n => n > 1) as number
// 如果不加上面的断言, num 的类型会被推断为 number | undefined
const absoulteNum: number = arr.find(n => n > 1) as number
// 如果不加上面的断言, 会报错: 类型 'number | undefined' 不能赋值给类型 'number'

// 或者使用尖括号
const num = <number>arr.find(n => n > 1)
// 但会和 JSX 语法冲突, 不能在 .tsx 中使用

非空断言

TypeScript 还提供了非空断言操作符 !, 可以用来断言一个值不为 nullundefined

1
2
3
4
const element = document.getElementById('app')!
// 如果不加上面的断言, element 的类型会被推断为 HTMLElement | null
// 会报错: 对象可能为 "null"。ts(2531)
element.innerHTML = 'Hello, TypeScript!'

接口

TypeScript 可以使用 interfacetype 来定义对象的成员及其类型, 二者区别见示例 ; 我一般在定义某个多次使用的对象时使用 type, 定义类、函数 props 时使用 interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义接口
interface Person {
name: string
age: number
// 可选成员
gender?: 'male' | 'female' | 'other'
phone?: string
// 只读成员, 类似于 const
readonly id: number
// 动态成员, 可以接受任意数量的成员, 但必须是 string 类型
[hobby: string]: string
}
// 使用接口
const leaf: Person = { name: 'leaf', age: 18, id: 1 }
function sayHi(person: Person): void {
console.log(`Hi, I'm ${person.name}, I'm ${person.age} years old`)
}
sayHi(leaf)

TypeScript 中的类和 ES6 中的类基本一致, 但有一些额外的特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
// ts 中, 实例成员必须在构造函数外指定类型
name: string
age: number
readonly id: number = 1
// 构造函数
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// 实例方法
sayHi(): void {
console.log(`Hi, I'm ${this.name}, I'm ${this.age} years old`)
}
}
  • TypeScript 中的类可以用 publicprivateprotected 来修饰成员:
    public: 默认, 所有地方都可以访问, 相当于 ES6 中的类成员
    private: 只能在类的内部访问, 相当于 ES6 中的 #
    protected: 只能在类的内部和子类中访问
  • class 中也可以使用 readonly 修饰成员, 表示只读

类的接口

多个类可能具有一些共同的特性, 但这些特性之间的实现又有一些小差异, 此时用父类可能不太合适, 可以使用接口来抽象这些共同的特性

TypeScript 中, 可以使用 implements 来在类中实现接口

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
31
32
// 定义接口
interface Eat {
eat(food: string): void
}
interface Say {
sayHi(): void
}
// 使用接口
class Cat implements Eat {
name: string
constructor(name: string) {
this.name = name
}
eat(food: string): void {
console.log(`喵喵喵, ${this.food}, 喵喵喵喵喵喵!`)
}
}
// 使用多个接口
class Person implements Eat, Say {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
eat(food: string): void {
console.log(`${this.name} is eating ${food}`)
}
sayHi(): void {
console.log(`Hi, I'm ${this.name}, I'm ${this.age} years old`)
}
}

抽象类

抽象类类似于上面的类的接口, 是多个类具有的一些公共方法, 但抽象类可以指定一些方法的实现, 而接口只能定义方法的签名; 抽象类使用 abstract 关键字来定义, 使用 extends 来继承

注意: 抽象类不能被实例化, 只能被继承

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
// 定义抽象类
abstract class Animal {
// 抽象方法, 和接口中的方法签名一样
abstract eat(food: string): void
abstract sayHi(): void
// 抽象类中可以定义非抽象方法, 指定默认实现
sleep(): void {
console.log('sleeping')
}
}
// 使用抽象类
class Cat extends Animal {
name: string
constructor(name: string) {
super() // 继承抽象类时, 必须在构造函数中调用 super
this.name = name
}
eat(food: string): void {
console.log(`喵喵喵, ${food}, 喵喵喵喵喵喵!`)
}
sayHi(): void {
console.log(`喵喵喵喵喵!`)
}
}
const cat = new Cat('Tom')
cat.eat('fish') // 喵喵喵, fish, 喵喵喵喵喵喵!
cat.sayHi() // 喵喵喵喵喵!
cat.sleep() // sleeping

泛型

泛型指的是在定义函数、接口或类的时候, 不预先指定具体的类型, 而在使用的时候再指定类型的一种特性

定义函数时, 可以使用 <T> 来定义泛型类型, T 可以是任意标识符, 相当于一个额外的参数, 用来接受传入的类型

1
2
3
4
5
6
7
8
9
// 定义泛型函数
// 创建一个指定长度和值的数组
function createArray<T>(length: number, value: T): T[] {
return Array.from({ length }, () => value)
}
// 使用泛型函数
const arr1 = createArray<string>(3, 'x') // ['x', 'x', 'x']
const arr2 = createArray<number>(3, 1) // [1, 1, 1]
const arr3 = createArray(3, 1) // [1, 1, 1], 根据传入的参数类型推断泛型类型

类型声明

一些第三方库的函数可能没有提供类型声明文件, 可以使用 declare 关键字来定义类型声明文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入第三方库
import { camelCase } from 'lodash'
// 定义类型声明文件
declare function camelCase(input: string): string

// 也可以对某个模块进行类型声明
import * as _ from 'lodash'
declare module 'lodash' {
export function camelCase(input: string): string
export function kebabCase(input: string): string
export function snakeCase(input: string): string
export function upperFirst(input: string): string
// ...
}

一些常见第三方库已经有了类型声明文件, 可以直接安装即可, 如 npm i @types/node -D(还有些库的声明文件是内置的, 不需要安装, 如下所示)

  • 标题: TypeScript学习笔记
  • 作者: 小叶子
  • 创建于 : 2024-03-06 08:57:02
  • 更新于 : 2024-05-20 15:13:42
  • 链接: https://blog.leafyee.xyz/2024/03/06/TypeScript学习笔记/
  • 版权声明: 版权所有 © 小叶子,禁止转载。
评论