Skip to content

TypeScript

TypeScript 是一种开源的编程语言,它是 JavaScript 的一个超集,意味着 TypeScript 包含了 JavaScript 的所有语法和特性,并在此基础上提供了额外的静态类型系统和一些新的特性。

环境搭建

安装

bash
npm install -D typescript
tsc -v

# ts-node 是一个 Node.js 的执行环境,它可以让你在 Node.js 环境中直接运行 TypeScript 代码。
npm install ts-node

# @types/node 是一个 TypeScript 类型定义文件的包,主要用于提供 Node.js API 的类型定义。
npm install @types/node

编译

bash
# 执行一次
npx tsc xxx.ts

# 监听并执行
npx tsc xxx.ts -w

# ts-node
ts-node xxx.ts

三斜线注释

三斜线注释以 /// 开头,通常位于 TypeScript 文件的顶部。它可以用于引入声明文件、模块导入声明以及指定其他编译选项。

引入声明文件

js
/// <reference types="lodash" />

模块导入声明

js
/// <reference path="./moduleA.ts" />

指定编译选项

js
/// <amd-module name="myModule" />

注意

随着 TypeScript 的版本升级,三斜线注释的使用已经不再推荐。推荐使用更现代的模块导入语法和配置文件来处理模块导入和编译选项。

类型声明

typescript
type Point = {
  x: number
  y: number
}

type Person = {
  name: string
  age: number
}

type User = Person & {
  username: string
}

type Status = 'active' | 'inactive'

type Result<T> = T extends string ? string : number

type Coordinates = [number, number]

type WorldString = `Hello, ${string}!`;
const myString: WorldString = `Hello, TypeScript!`; // 正确
const anotherString: WorldString = `Goodbye, TypeScript!`; // 类型错误

TIP

  • 与 typeof 结合使用
ts
let y = { a: 1, b: "hello" };
type YType = typeof y; // YType 的类型为 { a: number; b: string; }

let z: YType = { a: 2, b: "world" }; // 使用 YType 类型
  • 与 keyof 结合使用
js
interface Person {
  name: string;
  age: number;
  gender: string;
}

type PersonKeys = keyof Person; // 无效,为了演示用,可删除
// 等价于 type PersonKeys = "name" | "age" | "gender"

function getProperty(obj: Person, key: keyof Person) {
  return obj[key];
}

const person: Person = {
  name: "John",
  age: 30,
  gender: "male",
};

const username = getProperty(person, "name"); // 类型为 string
const age = getProperty(person, "age"); // 类型为 number
const gender = getProperty(person, "gender"); // 类型为 string

类型注解

类型注解是 TypeScript 中的一种语法特性,用于显式地声明变量、函数参数、函数返回值等的类型。通过类型注解,可以告诉编译器变量的预期类型,以进行静态类型检查。

类型注解使用冒号(:)后跟一个类型,将其附加到标识符上。

js
let age: number = 25 // 声明一个名为 age 的变量,类型为 number
let name: string = 'John' // 声明一个名为 name 的变量,类型为 string
let isStudent: boolean = true // 声明一个名为 isStudent 的变量,类型为 boolean

function greet(person: string): void {
  console.log('Hello, ' + person)
}

greet('Alice') // 调用 greet 函数,传入一个名为 person 的参数,类型为 string

TIP

类型注解不是强制的,因为 TypeScript 具有类型推断功能,可以根据上下文自动推断变量的类型。但是,使用类型注解可以提供更明确的类型信息,并在编码过程中提供更好的代码提示和静态类型检查。

操作符

非空断言操作符

非空断言操作符(Non-null Assertion Operator),表示为 !,是 TypeScript 中的一种语法特性,用于告诉编译器某个表达式的值是非空的,从而消除可能的空值类型检查警告。

js
let name: string | undefined = getUserName();

let length: number = name!.length;

console.log(length);

可选链操作符

可选链操作符(Optional Chaining Operator),表示为 ,是一种语法特性,在 TypeScript 中用于处理可能为 null 或 undefined 的属性访问或方法调用。

js
interface Person {
  name?: string;
  age?: number;
}

let person: Person | null = null

let name = person?.name
console.log(name) // 输出: undefined

let age = person?.age
console.log(age) // 输出: undefined

person = {
  name: 'John',
  age: 30
}

let name2 = person?.name
console.log(name2) // 输出: 'John'

let age2 = person?.age
console.log(age2) // 输出: 30

空值合并操作符

空值合并操作符(Nullish Coalescing Operator),表示为 ??,是一种用于处理可能为 null 或 undefined 的值的语法特性。它用于在两个值之间选择一个非空的值。

js
let name = null
let defaultName = 'John'

let result = name ?? defaultName
console.log(result) // 输出: 'John'

let age = 0
let defaultAge = 30

let result2 = age ?? defaultAge
console.log(result2) // 输出: 0

数据类型

Number

ts
const decimal: number = 6
const hex: number = 0xf00d
const binary: number = 0b1010
const octal: number = 0o744
const big: bigint = 100n

const numObj: Number = new Number(123);

String

ts
const color: string = 'blue'

const fullName: string = `Han Bobbing`
const sentence: string = `Hello, my name is ${fullName}.`

const strObj: String = new String("Hello, world!");

Boolean

ts
const isDone: boolean = false

const flagObj: Boolean = new Boolean(true);

null

typescript
const n: null = null

undefined

typescript
const u: undefined = undefined

BigInt

typescript
const b: bigint = 100n

Symbol

typescript
const s: symbol = Symbol()

Object

typescript
const a: Object = 123
const a1: Oject = '123'
const a2: Oject = false
const a3: Object = []
const a4: Object = {}
const a5: Object = () => 123

const a: object = '123' //报错
const a1: object = 123 //报错
const a2: object = false //报错
const a3: object = []
const a4: object = {}
const a5: object = () => 123

const a: {} = 123
const a1: {} = '123'
const a2: {} = []
const a3: {} = {}

TIP

  • Object 形式:任意类型均可赋值
  • object 形式:仅集合类型可赋值(如 Array, Function)
  • {}形式:任意类型均可赋值(同 Object 形式),但无法新增属性

Array

typescript
// 字面量
let arr: number[] = [1, 2, 3]
// 泛型
let arr: Array<number> = [1, 2, 3]
//接口
interface Person {
  name: string
  age: number
}

const people: Person[] = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
]

Function

  • 函数声明
typescript
function sum(x: number, y: number): number {
  return x + y
}

TIP

若函数没有返回值时,该函数类型默认为 void。

  • 函数表达式
typescript
const add = (a: number, b: number): number => {
  return a + b
}

const subtract = function (a: number, b: number): number {
  return a - b
}
  • 可选参数

? 表示可选的参数。

typescript
function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName
  } else {
    return firstName
  }
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName('Tom')

注意

可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了。

  • 剩余参数

...rest 的方式获取函数中的剩余参数只能是最后一个参数。

typescript
function push(array: any[], ...items: any[]) {
  items.forEach(function (item) {
    array.push(item)
  })
}

let a = []
push(a, 1, 2, 3)
  • 参数默认值

TypeScript 会将添加了默认值的参数识别为可选参数,且不受「可选参数必须接在必需参数后面」的限制了。

typescript
function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName
}
let tomcat = buildName('Tom', 'Cat')
let cat = buildName(undefined, 'Cat')

注意

若使用了可选参数?,则不能指定参数默认值。

  • 接口参数
js
interface Point {
  x: number;
  y: number;
}

function printPoint(point: Point) {
  console.log(`x: ${point.x}, y: ${point.y}`)
}

const myPoint: Point = { x: 10, y: 20 }
printPoint(myPoint)

字面量

字面量类型用来表示一组的明确的可选值列表,通常配合联合类型一起使用。

typescript
let color: 'red' | 'blue' | 'black'
let num: 1 | 2 | 3 | 4 | 5
let isBool: true

any

任意值(Any)用来表示允许赋值为任意类型。非必要,不使用!

typescript
let d: any = 4
d = 'hello'
d = true

注意

  • 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
  • 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

unknown

typescript
let notSure: unknown = 4
notSure = 'hello'

注意

  • 只能赋值给 unknow 和 any 类型,不能赋值给其他类型
  • 无法读取任何属性,无法调用任何方法(any 类型可以)
  • 比 any 类型更加安全,优先推选

void

在 TypeScript 中,void 表示没有返回值的函数或表达式。它表示函数不返回任何值,或者将变量的类型定义为 void 表示该变量不存储任何值。:

typescript
function alertName(): void {
  alert('My name is Tom')
}

声明一个 void 类型的变量只能赋值为 undefined 和 null:

typescript
let unusable: void = undefined

never

never 是 TypeScript 中的一个最底层类型,表示那些永远不会发生的值的类型。它表示无法到达的终点,或者会引发异常或无限循环的函数。在一些特定的情况下可以增加代码的安全性和可读性。

typescript
// 永远不会返回的函数
function error(message: string): never {
  throw new Error(message)
}

// 无限循环的函数
function infiniteLoop(): never {
  while (true) {
    // 无限循环
  }
}

注意

在联合类型中若使用 never 类型,则会被忽略。

类型优先级

any/unknow -> Object -> Number/String/Boolean -> number/string/boolean -> never

tuple

确切知道包含多少个元素, 以及特定索引对应的类型。

typescript
let x: [string, number] = ['hello', 10]

enum

enum 用来表示一组的明确的可选值列表,且会被编译为 JS 代码,功能与字面量 + 联合类型类似。每个枚举成员都代表一个命名常量,这些常量的默认值是从 0 开始自增的。

  • 数字枚举
typescript
// 默认自增值
enum Color {
  Red,
  Green,
  Blue
}
let c: Color = Color.Green

// 手动指定值
enum Color {
  Red = 1,
  Green,
  Blue
}
let c: Color = Color.Green

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4
}
let c: Color = Color.Green

enum Days {
  Sun = 7,
  Mon = 1,
  Tue,
  Wed,
  Thu,
  Fri,
  Sat
}

TIP

未赋值的枚举项会接着上一个枚举项递增,若未赋值的枚举项与已赋值的重复了,TypeScript 并不会报错,使用的时候需要注意,最好不要出现这种覆盖的情况。

typescript
enum Days {
  Sun = 3,
  Mon = 1,
  Tue,
  Wed,
  Thu,
  Fri,
  Sat
}

console.log(Days['Sun'] === 3) // true
console.log(Days['Wed'] === 3) // true
console.log(Days[3] === 'Sun') // false
console.log(Days[3] === 'Wed') // true
  • 字符串枚举
typescript
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

注意

在字符串枚举中没有自增长行为,每个枚举成员都必须具有字符串字面量值。

  • 异构枚举
ts
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = 'YES'
}

内置对象

  • Date
ts
const today: Date = new Date()
  • RegExp
ts
const regex: RegExp = /pattern/
  • Error
ts
const error: Error = new Error("Something went wrong")
  • Promise
ts
const promise: Promise<T> = new Promise((resolve, reject) => { })
  • Map / WeakMap
ts
const map: Map<K, V> = new Map()
  • Set / WeakSet
ts
const set: Set<T> = new Set()
  • HTMLElement
ts
const div: HTMLDivElement = document.querySelector('div') as HTMLDivElement
  • NodeList
ts
const nodeList: NodeList = document.querySelectorAll(".some-class")
  • XMLHttpRequest
ts
const xhr:XMLHttpRequest = new XMLHttpRequest()
  • Storage
ts
const sto: Storage = localStorage
  • Location
ts
const local: Location = location

注意

在 TypeScript 中,document.cookie 是用于操作和管理浏览器中的 Cookie 的属性。它的类型可以表示为 string。

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。

typescript
let myFavoriteNumber: string | number
myFavoriteNumber = 'seven'
myFavoriteNumber = 7

let arr: (number | string)[] = [1, 2, 'a', 'b', 3]

交叉类型

在 TypeScript 中,交叉类型(Intersection Types)允许将多个类型合并为一个新类型,其中新类型将具有所有原始类型的属性和方法。使用 & 符号可以表示交叉类型。

typescript
type Person = {
  name: string
  age: number
}

type Employee = {
  company: string
  position: string
}

type PersonWithJob = Person & Employee

const person: PersonWithJob = {
  name: 'John',
  age: 30,
  company: 'ABC Corp',
  position: 'Manager'
}

泛型

泛型函数

typescript
function identity<T>(arg: T): T {
  return arg
}

let result = identity<string>('Hello')

泛型接口

typescript
interface List<T> {
  length: number
  getItem(index: number): T
}

let numbers: List<number> = {
  length: 3,
  getItem(index: number): number {
    // 实现方法
  }
}

泛型类

typescript
class Container<T> {
  private value: T

  constructor(value: T) {
    this.value = value
  }

  getValue(): T {
    return this.value
  }
}

let container = new Container<number>(42)
let value = container.getValue()

泛型约束

泛型约束(Generic Constraints)是在 TypeScript 中用于限制泛型类型参数的一种机制。通过泛型约束,我们可以对泛型类型参数施加一定的条件,以确保只允许特定类型或具有特定属性的类型作为参数。

泛型约束使用 extends 关键字后跟一个类型来指定约束条件。以下是泛型约束的几种常见用法:

  • 约束为特定类型
js
function logLength<T extends string>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello"); // 输出: 5
logLength(10); // 错误,类型 'number' 没有 'length' 属性
  • 约束为具有特定属性的类型
js
interface Lengthable {
  length: number;
}

function logLength<T extends Lengthable>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello"); // 输出: 5
logLength([1, 2, 3]); // 输出: 3
logLength({ length: 10 }); // 输出: 10
logLength(10); // 错误,类型 'number' 没有 'length' 属性

泛型工具

  • Partial<T>:用于将一个类型的所有属性变为可选
ts
interface User {
    name: string;
    age: number;
}

type test = Partial<User>
 
//转换完成之后的结果
 
type test = {
    name?: string | undefined;
    age?: number | undefined;
}
 
//原理
type PratialUser<T,K extends keyof T> = {
    [P in K]?: T[P]
}
  • Required<T>:用于将一个类型的所有属性变为必选
ts
interface User {
    name?: string;
    age?: number;
}
//原理
type CustomRequired<T> = {
    [P in keyof T]-?: T[P]
}
 
type test = Required<User>
type test2 = CustomRequired<User>
 
//结果
interface User {
    name: string;
    age: number;
}
  • Pick<T, K>:用于从一个类型中选取指定的属性
ts
interface User {
    name?: string;
    age?: number;
}

type test = Pick<User,'age'>
 
//结果
type test = {
    age?: number | undefined;
}

//原理
type CoustomPick<T,K extends keyof T> = {
    [P in K]: T[P]
}
  • Exclude<T, K> :用于从一个类型的属性集合中排除指定的属性
ts
type test = Exclude<'a' | 'b' | 'c', 'a' | 'b'>
 
//结果
 type test = "c"

//原理
type CustomExclude<T,K> = T extends K ? never : T
  • Omit<T, K>:用于创建一个新类型,该新类型从原始类型中排除指定的属性
ts
interface User {
    address?: string;
    name?: string;
    age?: number;
}

type test = Omit<User,'age'>
 
//结果
 type test = {
    address?: string | undefined;
    name?: string | undefined;
}

//原理
type coustomOmit<T,K> = Pick<T,Exclude<keyof T,K>>
  • Record<T, K>:用于创建一个对象类型,其属性键来自集合 T,而属性值的类型则为 K
ts
type UserRole = "admin" | "user" | "guest";
type PermissionLevel = number;

const userPermissions: Record<UserRole, PermissionLevel> = {
  admin: 3,
  user: 2,
  guest: 1
};
  • ReturnType<T>:用于获取函数 T 的返回类型
ts
function greet(name: string) {
  return `Hello, ${name}!`;
}

type GreetReturnType = ReturnType<typeof greet>;
// GreetReturnType 的类型是 string

条件类型

ts
// 基本语法
type ConditionalType = T extends U ? X : Y;

// 原始类型
type IsNumber<T> = T extends number ? "Yes" : "No";

type Result1 = IsNumber<42>;        // 类型是 "Yes"
type Result2 = IsNumber<"hello">;   // 类型是 "No"

// 泛型中的条件类型
type Flatten<T> = T extends Array<infer R> ? R : T;

type Test1 = Flatten<string[]>;  // 类型是 string
type Test2 = Flatten<number>;    // 类型是 number

// 分布式条件类型
type NotNullable<T> = T extends null | undefined ? never : T;

type Test3 = NotNullable<string | number | null | undefined>;  // 类型是 string | number

infer

在 TypeScript 中,infer 关键字用于条件类型中,用于在类型推断的上下文中声明一个类型变量。

js
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function add(a: number, b: number): number {
  return a + b;
}

type ResultType = ReturnType<typeof add>; // ResultType 的类型将被推断为 number

// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;

type Test = ElementType<number[]>; // Test 类型是 number

// 提取 Promise 解析类型
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;

type AsyncFunctionReturn = UnpackPromise<Promise<string>>; // 类型是 string

WARNING

infer 关键字只能在 extends 条件类型子句中使用。它声明了一个待推断的类型变量,可以在条件的真分支中使用。这种方式使得在类型操作中可以引用之前无法直接访问的类型。

类型断言

有些情况下,变量的类型对于我们来说是很明确,但是 TS 编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:

尖括号语法

typescript
let someValue: unknown = 'this is a string'
let strLength: number = (<string>someValue).length

as 语法

ts
let someValue: unknown = 'this is a string'
let strLength: number = (someValue as string).length

注意

  • 类型断言不会对运行时的值进行任何改变或转换,它仅仅是在编译阶段提供了类型的信息
  • 在使用类型断言时,需要小心确保你的断言是正确的,否则可能会导致类型错误
  • 合理使用类型断言可以在某些情况下提供更灵活的类型处理方式,但过度使用或滥用类型断言可能会导致类型安全性降低

类型推论

声明变量并立即赋值,但没有明确的指定类型,TypeScript 会根据赋值类型推测出一个类型。 声明函数没有明确指定返回值类型,TypeScript 会根据函数参数类型推测出一个类型。

typescript
let myFavoriteNumber = 'seven'
//等价于
let myFavoriteNumber: string = 'seven'

function Sum(num1: number, num2: number) {
  return num1 + num2
}
//等价于
function Sun(num1: number, num2: number): number {
  return num1 + num2
}

注意

  • 变量声明未立即赋值,则必须手动添加类型声明,否则会被推断成 any 类型而完全不被类型检查。
  • 函数声明未指定返回值类型,则函数参数必须指定类型,否则会出现意外的错误。

命名空间

ts
namespace MyNamespace {
  export const name = 'John';

  export function greet() {
    console.log('Hello, ' + name);
  }

  export class Person {
    constructor(public age: number) {}

    sayAge() {
      console.log('I am ' + this.age + ' years old.');
    }
  }
}

MyNamespace.greet(); // 调用命名空间中的函数 greet
const person = new MyNamespace.Person(25); // 创建命名空间中的类 Person 的实例
person.sayAge(); // 调用命名空间中类 Person 的方法 sayAge

面向对象

类 Class

typescript
class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
  }
}

// 创建一个 Person 实例
const person = new Person('John', 25)
person.sayHello() // 输出: Hello, my name is John and I'm 25 years old.

extends

类继承是面向对象编程的一种机制,它允许一个类(子类)继承另一个类(父类)的属性和方法,并且可以通过扩展和重写来添加特定的行为。

typescript
class Animal {
  name: string

  constructor(name: string) {
    this.name = name
  }

  sayHello() {
    console.log(`Hello, I'm ${this.name}.`)
  }
}

class Dog extends Animal {
  breed: string

  constructor(name: string, breed: string) {
    super(name) // 调用父类的构造函数
    this.breed = breed
  }

  bark() {
    console.log('Woof! Woof!')
  }
}

// 创建一个 Dog 实例
const dog = new Dog('Buddy', 'Golden Retriever')
dog.sayHello() // 输出: Hello, I'm Buddy.
dog.bark() // 输出: Woof! Woof!
console.log(dog.breed) // 输出: Golden Retriever

TIP

在 TypeScript 中,当一个子类继承了父类时,子类的构造函数必须调用父类的构造函数,以确保父类的属性和方法得到正确的初始化。

如果子类没有显式地调用父类的构造函数,那么 TypeScript 会默认在子类的构造函数中添加 super() 调用,以调用父类的构造函数。

WARNING

在 TypeScript 中,可以省略调用父类构造函数 super() 的情况有两种:

  • 当子类没有定义自己的构造函数时,TypeScript 会默认添加一个没有参数的构造函数,并自动调用父类的构造函数。这种情况下,可以省略 super() 的显式调用。
  • 当子类的构造函数中的参数和父类的构造函数中的参数完全一致时,可以省略 super() 的显式调用。

DANGER

需要注意的是,如果子类定义了自己的构造函数,并且参数与父类的构造函数不完全一致,那么就必须在子类的构造函数中显式调用 super(),以确保父类的构造函数被正确调用,并完成必要的属性初始化。否则,编译器会报错。

implements

接口是一种定义了一组属性和方法的合同,类可以通过实现接口来保证它们遵循了接口定义的结构。

typescript
interface Printable {
  print(): void
}

class Book implements Printable {
  title: string
  author: string

  constructor(title: string, author: string) {
    this.title = title
    this.author = author
  }

  print() {
    console.log(`Title: ${this.title}, Author: ${this.author}`)
  }
}

const myBook = new Book('The Great Gatsby', 'F. Scott Fitzgerald')
myBook.print()

implements 和 extends 区别

  • implements 用于表示一个类实现接口,确保类遵循接口定义的属性和方法。
  • extends 用于表示一个类继承另一个类,继承父类的属性和方法,并可以进一步扩展或覆盖它们。

注意

需要注意的是,一个类可以同时使用 implements 和 extends,表示它既实现了接口,又继承了另一个类。这种情况下,extends 关键字通常在 implements 关键字之前。

public

在 TypeScript 中,public 是一种访问修饰符(Access Modifier),用于指定类的成员(属性和方法)的访问权限。

当成员标记为 public 时,它们可以在类内部和外部被访问。

如果不指定访问修饰符,默认情况下成员是 public ,可省略不写。

protected

protected 是一种访问修饰符(Access Modifier),用于指定类的成员(属性和方法)的受保护访问权限。

当成员标记为 protected 时,它们可以在类内部访问,以及在派生类(子类)中访问,但不能在类的外部直接访问。

typescript
class Person {
  protected name: string
  protected age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  protected sayHello() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
  }
}

class Student extends Person {
  private studentId: number

  constructor(name: string, age: number, studentId: number) {
    super(name, age)
    this.studentId = studentId
  }

  public introduce() {
    console.log(`I'm a student with ID ${this.studentId}.`)
    this.sayHello() // 可以访问父类的受保护方法
  }
}

const student = new Student('John', 20, 12345)
student.introduce() // 输出: I'm a student with ID 12345. Hello, my name is John and I'm 20 years old.
console.log(student.name) // 错误,无法访问受保护属性
student.sayHello() // 错误,无法访问受保护方法

private

private 是一种访问修饰符(Access Modifier),用于指定类的成员(属性和方法)的私有访问权限。

当成员标记为 private 时,它们只能在类内部被访问,而不能在类的外部或派生类中被访问。

typescript
class Person {
  private name: string
  private age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  private sayHello() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
  }
}

const person = new Person('John', 25)
console.log(person.name) // 错误,无法访问私有属性
person.sayHello() // 错误,无法调用私有方法

static

static 用于定义类的静态成员。静态成员属于类本身,而不是类的实例。可以通过类名直接访问静态成员,而不需要创建类的实例。

js
class MathUtility {
  static PI: number = 3.14159

  static calculateCircumference(radius: number): number {
    return 2 * MathUtility.PI * radius
  }
}

const mathUtil = new MathUtility()
console.log(mathUtil.PI) // Error: 静态成员 "PI" 无法通过实例访问
console.log(mathUtil.calculateCircumference(5)) // Error: 静态方法 "calculateCircumference" 无法通过实例调用
console.log(MathUtility.PI) // 输出: 3.14159
console.log(MathUtility.calculateCircumference(5)) // 输出: 31.4159

readonly

readonly 是一种修饰符(Modifier),用于标记类的成员(属性)为只读。只读属性只能在类的构造函数或声明时进行初始化,并且在初始化后不能再被修改。

typescript
class Person {
  readonly name: string
  readonly age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  updateName(newName: string) {
    // 错误,只读属性无法被修改
    this.name = newName
  }
}

const person = new Person('John', 25)
console.log(person.name) // 输出: John
person.name = 'Tom' // 错误,只读属性无法被修改

注意

  • readonly 只能修饰属性,不能修饰方法
  • readonly 修饰的属性必须声明类型,否则该属性类型与const类似
  • readonly 和其他访问修饰符同时存在的话,需要写在其后面。
  • 接口或{}表示的对象类型也可以使用 readonly

getter 和 setter

js
class Person {
  private _name: string;

  constructor(value: string) {
    this._name = value;
  }

  get name(): string {
    return this._name;
  }

  set name(value: string) {
    this._name = value;
  }
}

const person = new Person("Jack");
console.log(person.name);
person.name = "Alice"; // 调用 setter 方法设置属性值
console.log(person.name); // 调用 getter 方法获取属性值,输出: "Alice"

抽象类 Abstract

在 TypeScript 中,抽象类是一种特殊的类,用于作为其他类的基类,并且不能直接实例化。抽象类用于定义一组共同的特征和行为,供其他类继承和实现。

js
abstract class Animal {
  abstract name: string; // 抽象属性,没有具体的实现,子类必须实现该属性

  abstract makeSound(): void; // 抽象方法,没有具体的实现,子类必须实现该方法

  move(): void {
    console.log("The animal is moving.");
  }
}

class Dog extends Animal {
  name: string; //必须实现继承过来的抽象属性

  constructor(name: string) {
    super();
    this.name = name;
  }

  makeSound(): void { // 必须实现继承过来的抽象方法
    console.log("The dog barks.");
  }
}

const dog = new Dog("Buddy");
dog.makeSound(); // 输出: "The dog barks."
dog.move(); // 输出: "The animal is moving."
console.log(dog.name); // 输出: "Buddy"

注意

抽象属性和抽象方法都使用 abstract 关键字进行声明。抽象属性没有具体的实现,只有类型定义。抽象方法没有具体的方法体,只有方法签名。子类必须实现抽象属性和抽象方法,提供具体的实现逻辑。

接口 Interface

interface 是用于定义对象的类型的一种方式。你可以使用 interface 来描述对象的结构、属性和方法,并在声明变量或函数时使用这些接口类型。

js
interface Person {
  name: string;
  age: number;
}

const person: Person = {
  name: 'John',
  age: 25
}

接口合并

在 TypeScript 中,当你定义多个同名的接口时,它们会自动合并成一个接口,而不会引发冲突。这种合并规则被称为接口合并。

js
interface Person {
  name: string;
  age: number;
}

interface Person {
  gender: string;
}

const person: Person = {
  name: 'John',
  age: 25,
  gender: 'male'
}

TIP

接口合并的规则如下:

  • 对于同名的非函数成员,如果它们的类型是相同的,那么它们将被视为一个成员。
  • 对于同名的非函数成员,如果它们的类型不同,那么会引发一个编译错误。
  • 对于同名的函数成员,它们会被视为重载函数,合并后的接口将具有所有重载函数的签名。

接口继承

接口继承是另一种使用接口的方式,它允许一个接口继承另一个接口的成员。接口继承使用关键字 extends,并且可以继承一个或多个接口。

js
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
}

const dog: Dog = {
  name: 'Buddy',
  age: 3,
  breed: 'Labrador'
}

可选属性

在 TypeScript 中,你可以使用可选属性(Optional Properties)来定义对象中的属性是可选的,即可以存在也可以不存在。

js
interface Person {
  name: string;
  age?: number;
  gender?: string;
}

const person1: Person = {
  name: 'John',
  age: 25
}

const person2: Person = {
  name: 'Jane',
  gender: 'female'
}

只读属性

在 TypeScript 中,你可以使用 readonly 关键字来定义只读属性,即属性的值在赋值后不能被修改。

js
interface Person {
  readonly name: string;
  readonly age: number;
}

const person: Person = {
  name: "John",
  age: 25,
};

person.name = "Jane";
// Error: Cannot assign to 'name' because it is a read-only property.

任意属性

在 TypeScript 中,你可以使用任意属性(Index Signatures)来定义对象中允许有额外属性的情况。通过任意属性,你可以定义对象中除了已知属性之外的其他属性。

js
interface Person {
  name: string;
  age: number;
  [key: string]: any;
}

const person: Person = {
  name: 'John',
  age: 25,
  gender: 'male',
  occupation: 'Engineer'
}

定义函数类型

在 TypeScript 中,你可以使用函数类型来定义函数的类型,包括函数的参数类型和返回值类型。这样可以在编译时进行类型检查,确保函数的正确使用和调用。

js
interface Person {
  name: string;                // 固定属性
  age?: number;                // 可选属性
  readonly id: string;         // 只读属性
  [key: string]: any;          // 任意属性
  greet: (message: string) => void;  // 函数
}

const person: Person = {
  name: "John",
  id: "123",
  greet: (message) => {
    console.log(`${person.name} says ${message}`);
  }
};

person.age = 25;           // 设置可选属性
// person.id = "456";      // Error: 无法分配到 "id" ,因为它是只读属性
person.gender = "male";    // 任意属性
person.greet("Hello");     // 输出: John says Hello

tsconfig.json

要先在项目根目录下创建一个 ts 的配置文件 tsconfig.json 或执行 tsc --init 命令自动生成。

json
{
  "compilerOptions": {
    "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
    "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
    "diagnostics": true, // 打印诊断信息
    "target": "ES5", // 目标语言的版本
    "module": "CommonJS", // 生成代码的模板标准
    "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
    "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
    "allowJS": true, // 允许编译器编译JS,JSX文件
    "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
    "outDir": "./dist", // 指定输出目录
    "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
    "declaration": true, // 生成声明文件,开启后会自动生成声明文件
    "declarationDir": "./file", // 指定生成声明文件存放目录
    "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
    "sourceMap": true, // 生成目标文件的sourceMap文件
    "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
    "declarationMap": true, // 为声明文件生成sourceMap
    "typeRoots": [], // 声明文件目录,默认时node_modules/@types
    "types": [], // 加载的声明文件包
    "removeComments": true, // 删除注释
    "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
    "noEmitOnError": true, // 发送错误时不输出任何文件
    "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
    "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
    "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
    "strict": true, // 开启所有严格的类型检查
    "alwaysStrict": true, // 在代码中注入'use strict'
    "noImplicitAny": true, // 不允许隐式的any类型
    "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
    "strictFunctionTypes": true, // 不允许函数参数双向协变
    "strictPropertyInitialization": true, // 类的实例属性必须初始化
    "strictBindCallApply": true, // 严格的bind/call/apply检查
    "noImplicitThis": true, // 不允许this有隐式的any类型
    "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
    "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
    "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
    "noImplicitReturns": true, //每个分支都会有返回值
    "esModuleInterop": true, // 允许export=导出,由import from 导入
    "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
    "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
    "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
    "paths": {  // 路径映射,相对于baseUrl
      // 注意:实际上,TypeScript 本身并不直接支持路径别名。必须使用某种构建工具来解析 TypeScript 路径别名,并将其转换为有效的模块导入。
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@views/*": ["src/views/*"]
    },
    "rootDirs": ["src", "out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
    "listEmittedFiles": true, // 打印输出文件
    "listFiles": true // 打印编译的文件(包括引用的声明文件)
  },

  // 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
  "include": ["src/**/*"],

  // 指定一个排除列表(include的反向操作)
  "exclude": ["demo.ts"],

  // 指定哪些文件使用该配置(属于手动一个个指定文件)
  "files": ["demo.ts"]
}

*.d.ts

声明文件(Declaration File)用于描述 JavaScript 库、模块或全局变量的类型信息,以便在 TypeScript 项目中进行类型检查和静态分析。声明文件通常以 .d.ts 后缀命名。

需要注意的是,并非所有的 JavaScript 库都有现成的声明文件可用,特别是第三方或较新的库。在这种情况下,你可以根据库的文档和源代码编写自己的声明文件,或者使用一些工具和库来自动生成声明文件,

手动创建

通常用于声明全局变量和函数、模块、类、接口、类型别名,还可以使用其他高级的声明文件语法和特性,例如索引签名、函数重载、泛型等。

  • 声明全局变量和函数
js
// global.d.ts
declare const globalVariable: string;
declare function globalFunction(): void;
  • 声明模块
js
// mymodule.d.ts
declare module 'mymodule' {
  export function myFunction(): void;
  export const myVariable: number;
}
  • 声明类和接口
js
// myclass.d.ts
declare class MyClass {
  constructor(name: string);
  sayHello(): void;
}

declare interface MyInterface {
  method(): void;
}
  • 声明类型别名
js
// mytypes.d.ts
declare type MyType = {
  name: string,
  age: number
}

TIP

在编写声明文件时,有一些编写规范和最佳实践可以遵循:

  • 使用 declare 关键字开头来声明类型。
  • 使用 export 关键字来导出声明。
  • 使用正确的命名约定,如文件名以 .d.ts 结尾,模块声明文件以模块名命名等。
  • 提供详细和准确的类型信息,包括参数类型、返回类型等。
  • 参考相关库或框架的文档和源代码,以确保声明文件与实际行为一致。
  • 验证声明文件的正确性和有效性,可以使用 TypeScript 编译器进行类型检查。

自动安装

bash
npm install @types/package-name --save-dev

TIP

  • 确保你的项目已经使用了 npm 初始化,并且有一个 package.json 文件。
  • 使用的库或模块的声明文件是否已经存在。若存在,通常声明文件的包名是 @types/package-name,遵循 @types 组织的命名约定;若不存在,需要手动创 typeName.d.ts 建声明文件。
  • 安装成功后,声明文件将自动添加到你的项目的 node_modules/@types 目录下。

读书、摄影、画画、弹琴、编程