← Frontend

Typescript基础

TypeScript 基础语法和核心概念

一、TS 概念理论

TS 基础铺垫

1. 什么是 TS

TypeScript(TS) 是由微软开发的 JavaScript 的超集,在 JS 的基础上添加了静态类型系统,最终会被编译为纯 JavaScript 运行。

  • 超集关系: JS ⊂ TS,所有合法的 JS 代码都是合法的 TS 代码
  • 静态类型: 在编译阶段就能发现类型错误,而不是运行时
  • 强大的 IDE 支持: 代码补全、类型推断、重构更安全

💡 提示: TS 不能直接在浏览器或 Node.js 中运行,需要先通过 tsc 编译为 JS。

对比项 JavaScript TypeScript
类型系统 动态类型 静态类型
错误发现时机 运行时 编译时
代码可维护性 一般 强
学习成本 低 中等
适用场景 小型项目 中大型项目

2. TS 基础类型与写法

TS 中类型注解语法为 变量名: 类型:

// 基础类型
let isDone: boolean = false          // 布尔值
let count: number = 42               // 数字(整数/浮点数统一为 number)
let name: string = 'Shea'            // 字符串
let nothing: null = null             // null
let undef: undefined = undefined     // undefined
let sym: symbol = Symbol('id')       // symbol
let big: bigint = 100n               // bigint(ES2020+)

// 特殊类型
let anyVal: any = '任意类型'         // any:关闭类型检查(慎用!)
let unknownVal: unknown = 42         // unknown:比 any 更安全的宽泛类型
let noReturn: void = undefined       // void:函数无返回值
function throwErr(): never {         // never:永不返回(抛出异常/死循环)
  throw new Error('error')
}

⚠️ 注意: any 会绕过所有类型检查,大量使用会让 TS 失去意义,尽量用 unknown 替代。


3. TS 断言初步

断言是告诉编译器「我比你更了解这个值的类型」,分为三种:

i. 类型断言(Type Assertion)
// 写法一:尖括号语法(不推荐在 JSX 中使用)
let val: unknown = 'hello'
let len1: number = (<string>val).length

// 写法二:as 语法(推荐)
let len2: number = (val as string).length

// 双重断言:先断言为 unknown,再断言为目标类型(慎用)
let num = (someValue as unknown) as number
ii. 非空断言(Non-null Assertion)

使用 ! 告诉编译器该值不为 null 或 undefined:

function processInput(input: string | null) {
  // 不加 ! 会报错:Object is possibly 'null'
  console.log(input!.toUpperCase()) // 断言 input 一定不为 null
}

// DOM 操作中常见
const button = document.getElementById('btn')!  // 断言元素一定存在
button.addEventListener('click', () => {})

🚨 警告: 非空断言只是告诉编译器不报错,若运行时确实为 null,仍会抛出异常。

iii. 赋值断言(Definite Assignment Assertion)

用于类属性,告诉编译器属性一定会在使用前被赋值:

class MyClass {
  // 使用 ! 告诉 TS:我保证 value 在使用前会被赋值
  value!: string

  init() {
    this.value = 'initialized'
  }
}

TS 类型认知

4. 联合类型 / 交叉类型

类型 符号 含义 比喻
联合类型 | A 或 B,满足其中一个即可 并集(A ∪ B)
交叉类型 & A 且 B,必须同时满足 交集(A ∩ B)合并
// ─── 联合类型(Union Types)───
type StringOrNumber = string | number

let id: StringOrNumber = 'abc'  // ✅
id = 123                         // ✅
// id = true                     // ❌ 不允许

// 函数参数联合类型
function formatInput(val: string | number): string {
  // 使用前需要收窄类型(类型守卫)
  if (typeof val === 'string') {
    return val.toUpperCase()
  }
  return val.toFixed(2)
}

// ─── 交叉类型(Intersection Types)───
type Person = { name: string; age: number }
type Employee = { company: string; role: string }

// 合并两个类型,必须同时具备所有属性
type Staff = Person & Employee

const staff: Staff = {
  name: 'Shea',
  age: 25,
  company: 'Acme',
  role: 'Frontend Dev'
}

5. 类型守卫(Type Guards)

类型守卫是在运行时收窄(narrow)类型范围的机制,让 TS 在某个代码块中知道更具体的类型。

i. in 操作符
type Cat = { meow: () => void }
type Dog = { bark: () => void }

function makeSound(animal: Cat | Dog) {
  if ('meow' in animal) {
    // 此处 TS 知道 animal 是 Cat
    animal.meow()
  } else {
    // 此处 TS 知道 animal 是 Dog
    animal.bark()
  }
}
ii. typeof 操作符
function double(val: string | number) {
  if (typeof val === 'string') {
    // 此处 val 被收窄为 string
    return val.repeat(2)
  }
  // 此处 val 被收窄为 number
  return val * 2
}

💡 提示: typeof 守卫适用于基础类型:string、number、boolean、symbol、bigint、undefined、function。

iii. instanceof 操作符
class HttpError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message)
  }
}

function handleError(error: Error) {
  if (error instanceof HttpError) {
    // 此处 error 被收窄为 HttpError
    console.log(error.statusCode)
  } else {
    console.log(error.message)
  }
}
iv. 自定义类型守卫(Type Predicates)

使用 参数 is 类型 语法定义返回类型谓词的函数:

type Fish = { swim: () => void }
type Bird = { fly: () => void }

// 返回类型 `pet is Fish` 就是类型谓词
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim() // TS 知道这里是 Fish
  } else {
    pet.fly()  // TS 知道这里是 Bird
  }
}

3 TS 基础类型语法

6. 数组、对象、函数

// ─── 数组 ───
let nums: number[] = [1, 2, 3]            // 写法一
let strs: Array<string> = ['a', 'b']      // 写法二(泛型写法)
let tuple: [string, number] = ['age', 25] // 元组:固定长度和类型

// ─── 对象 ───
// 使用对象字面量类型
let user: { name: string; age: number; email?: string } = {
  name: 'Shea',
  age: 25
  // email 是可选属性,可以不写
}

// 索引签名:动态 key
let dict: { [key: string]: number } = {
  apple: 1,
  banana: 2
}

// ─── 函数 ───
// 参数类型 + 返回值类型
function add(a: number, b: number): number {
  return a + b
}

// 箭头函数类型
const greet = (name: string): string => `Hello, ${name}!`

// 可选参数(必须放在必填参数后面)
function buildName(firstName: string, lastName?: string): string {
  return lastName ? `${firstName} ${lastName}` : firstName
}

// 默认参数
function createUser(name: string, role: string = 'user') {
  return { name, role }
}

// 剩余参数
function sum(...nums: number[]): number {
  return nums.reduce((acc, n) => acc + n, 0)
}

// 函数重载
function format(x: string): string
function format(x: number): string
function format(x: string | number): string {
  return String(x)
}

7. 接口(Interface)

接口(Interface)用于定义对象的结构契约,是 TS 中描述对象形状的核心方式。

// 基础接口定义
interface User {
  id: number
  name: string
  age?: number            // 可选属性
  readonly email: string  // 只读属性,赋值后不可修改
}

// 接口继承(支持多继承)
interface Admin extends User {
  permissions: string[]
}

// 接口合并(同名接口自动合并)
interface Window {
  myProp: string
}
interface Window {
  myMethod(): void
}
// 合并结果:Window 同时具有 myProp 和 myMethod

// 函数类型接口
interface SearchFunc {
  (source: string, subString: string): boolean
}

const search: SearchFunc = (src, sub) => src.includes(sub)

// 索引接口
interface StringArray {
  [index: number]: string
}
对比项 interface type
描述对象/类 ✅ 更语义化 ✅ 可以
描述基础类型/联合/交叉 ❌ 不支持 ✅ 支持
同名自动合并 ✅ 支持 ❌ 报错
extends 继承 ✅ 支持 ✅ 可用 &
推荐使用场景 对象结构、类实现 复杂类型别名

8. 类(Class)

TS 对 ES6 类进行了增强,添加了访问修饰符、抽象类等特性:

// 访问修饰符
class Person {
  public name: string      // 公开(默认),外部可访问
  private age: number      // 私有,只能在类内部访问
  protected id: number     // 受保护,可在类内部和子类访问
  readonly birth: Date     // 只读

  // 构造函数参数简写(自动声明并赋值属性)
  constructor(
    public username: string,
    private password: string
  ) {
    this.name = username
    this.age = 0
    this.id = Math.random()
    this.birth = new Date()
  }

  // getter / setter
  get displayName(): string {
    return `[${this.id}] ${this.name}`
  }

  set displayName(val: string) {
    this.name = val
  }
}

// 类实现接口
interface Printable {
  print(): void
}

class Document implements Printable {
  print() {
    console.log('Printing...')
  }
}

// 抽象类:不能直接实例化,只能被继承
abstract class Shape {
  abstract getArea(): number  // 子类必须实现

  toString() {
    return `Area: ${this.getArea()}`
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super()
  }

  getArea(): number {
    return Math.PI * this.radius ** 2
  }
}

TS 进阶

10. 泛型(Generics)

泛型是 TS 最强大的特性之一,让函数、类、接口能够处理多种类型而不丢失类型信息。

// ─── 泛型函数 ───
// T 是类型参数,调用时由 TS 自动推断或手动传入
function identity<T>(arg: T): T {
  return arg
}

identity<string>('hello')  // 手动指定
identity(42)               // 自动推断为 number

// 泛型数组
function getFirst<T>(arr: T[]): T {
  return arr[0]
}

// ─── 泛型约束(extends)───
interface Lengthwise {
  length: number
}

// 约束 T 必须具有 length 属性
function logLength<T extends Lengthwise>(arg: T): number {
  return arg.length
}

logLength('hello')  // ✅ string 有 length
logLength([1, 2])   // ✅ array 有 length
// logLength(42)    // ❌ number 没有 length

// ─── 泛型接口 ───
interface ApiResponse<T> {
  code: number
  message: string
  data: T
}

type UserResponse = ApiResponse<{ name: string; age: number }>

// ─── 泛型类 ───
class Stack<T> {
  private items: T[] = []

  push(item: T): void {
    this.items.push(item)
  }

  pop(): T | undefined {
    return this.items.pop()
  }
}

const stack = new Stack<number>()
stack.push(1)

// ─── 常用内置泛型工具类型 ───
type Partial<T> = { [P in keyof T]?: T[P] }       // 所有属性变可选
type Required<T> = { [P in keyof T]-?: T[P] }      // 所有属性变必填
type Readonly<T> = { readonly [P in keyof T]: T[P] }// 所有属性变只读
type Pick<T, K extends keyof T> = { [P in K]: T[P] }// 选取部分属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> // 排除属性
type Record<K extends keyof any, T> = { [P in K]: T } // 键值映射

// 使用示例
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>
type PartialTodo = Partial<Todo>

11. 装饰器(Decorators)

装饰器是一种元编程特性,本质是一个函数,用于修改类、方法、属性等的行为。

⚠️ 注意: 使用装饰器需要在 tsconfig.json 中开启 "experimentalDecorators": true。

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
// ─── 类装饰器 ───
// 接收构造函数作为参数
function sealed(constructor: Function) {
  Object.seal(constructor)           // 密封类本身
  Object.seal(constructor.prototype) // 密封原型
}

@sealed
class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }
}

// ─── 方法装饰器 ───
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value
  // 替换原始方法,添加日志
  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${key},参数:`, args)
    const result = original.apply(this, args)
    console.log(`${key} 返回:`, result)
    return result
  }
  return descriptor
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b
  }
}

// ─── 属性装饰器 ───
function readonly(target: any, key: string) {
  Object.defineProperty(target, key, {
    writable: false
  })
}

class Point {
  @readonly
  PI = 3.14159
}

// ─── 参数装饰器 ───
function required(target: any, key: string, index: number) {
  console.log(`${key} 的第 ${index} 个参数是必须的`)
}

class Service {
  greet(@required name: string) {
    return `Hello, ${name}`
  }
}
装饰器类型 参数 使用位置
类装饰器 constructor @Dec class Foo {}
方法装饰器 target, key, descriptor @Dec method() {}
属性装饰器 target, key @Dec prop: type
参数装饰器 target, key, paramIndex method(@Dec param)
访问器装饰器 target, key, descriptor @Dec get/set prop

二、TS 编译运行机制

编译流程

TS 源码 (.ts)
    ↓  tsc 编译器
    ↓  1. 词法分析 → Token 流
    ↓  2. 语法分析 → AST(抽象语法树)
    ↓  3. 类型检查(Binder + Checker)
    ↓  4. 代码生成(Emitter)
JS 代码 (.js)

tsconfig.json 常用配置

{
  "compilerOptions": {
    "target": "ES2020",         // 编译目标版本
    "module": "CommonJS",       // 模块系统
    "strict": true,             // 开启所有严格检查
    "noImplicitAny": true,      // 禁止隐式 any
    "strictNullChecks": true,   // 开启 null/undefined 检查
    "outDir": "./dist",         // 编译输出目录
    "rootDir": "./src",         // 源码根目录
    "declaration": true,        // 生成 .d.ts 类型声明文件
    "sourceMap": true,          // 生成 source map
    "paths": {                  // 路径别名
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

TS 在项目中的运行方式

方式 工具 适用场景
预编译 tsc 生产环境打包
实时编译 ts-node Node.js 开发/脚本
构建工具集成 Vite / Webpack + ts-loader 前端工程(React/Vue)
按需转译 esbuild / swc 高性能构建(不做类型检查)

💡 提示: 在 Vite 项目中,TS 由 esbuild 负责转译(不做类型检查),类型检查由 vue-tsc 或 tsc --noEmit 单独运行。


参考资料