TypeScript

【TypeScript】深入学习TypeScript模块 最近博主一直在创作TypeScript的内容,所有的TypeScript文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️ 本篇文章将深入去讲解TypeScript中的模块化,这也许会是你看过的最全面最细致的TypeScript............... 阅读详情

文章目录


作为前端开发者,你是不是也遇到过这些坑?比如写了个函数传了字符串,却被当成数字用,运行时才报错;或者接手别人的项目,变量没注释,根本不知道该传什么值?TypeScript(简称 TS) 就是来解决这些问题的 —— 它给 JavaScript 加了 “类型保险”,让代码写得放心、改得省心,而且所有 JS 代码都能直接用,不用怕兼容问题。

一、为什么一定要学 TypeScript?

  1. 写代码时 “提前排雷”,不用等运行才报错

    JavaScript 是 “运行时才翻脸” 的 —— 比如你给一个存年龄的变量赋值字符串 "18",写的时候不报错,用户用的时候才崩。而 TS 在你写代码时就会提醒:“这里要数字,不能传字符串!”

    举个例子:

    // JS 里不报错,运行时才出问题
    let age = 18
    age = "18" // 写的时候没事,后续用 age 做计算就会NaN
    
    // TS 里直接报错,提前修改
    let age: number = 18
    age = "18" // 红色波浪线警告:“string 不能赋值给 number”
    
  2. IDE 帮你 “猜下一步”,写代码更快

    有了类型信息,VS Code 能帮你自动补全代码、提示属性。比如你定义了一个用户对象 user: { name: string; age: number },输入 user. 时,IDE 会直接弹出 name 和 age 让你选,不用记属性名,也不用翻文档。

  3. 代码自带 “说明书”,团队协作不懵

    不用写一堆注释,类型就是最好的文档。比如函数 function getUserInfo(id: number): User,一眼就知道 “要传数字 ID,返回用户对象”,新人接手也能快速明白逻辑。

  4. 完全兼容 JS,不用 “推倒重写”

    你现有的 JS 项目,把文件后缀改成 .ts 就能用,想什么时候加类型就什么时候加。比如先把核心函数加类型,其他代码慢慢改,没有迁移压力。

二、TypeScript 环境搭建

TS 不能直接运行,要先编译成 JS。

1. 快速试手:3 步跑通第一个 TS 程序

  • 第一步:装 Node.js

    TS 编译器需要 Node.js 支持,去 Node.js 官网 下 “LTS 版本”(长期支持版,更稳定),一路下一步安装。

    装完打开命令行,输入 node -v 和 npm -v,能看到版本号就装好了(比如 v18.17.0 和 9.6.7)。

  • 第二步:装 TS 编译器

    命令行输入:

    npm install -g typescript
    

    装完输 tsc -v,看到 Version 5.2.2 这样的版本号,就说明编译器装好了。

  • 第三步:写代码、编译、运行

  1. 新建一个文件夹(比如叫 ts-demo),里面新建文件 hello.ts,写代码:

    // 定义一个函数,参数是字符串,返回也是字符串
    function sayHello (name: string): string {
      return `Hello!我是 ${name},这是我的第一个 TS 程序`
    }
    
    // 调用函数并打印
    console.log(sayHello("TypeScript"));
    
  2. 编译:在命令行进入 ts-demo 文件夹,输入 tsc hello.ts,会自动生成 hello.js(TS 代码编译后的 JS 文件,类型注解会被去掉)。

  3. 运行:输入 node hello.js,就能看到输出 Hello!我是 TypeScript,这是我的第一个 TS 程序。

2. 实际项目:用 tsconfig.json 统一配置

如果要写正经项目,不能每次都手动编译单个文件,需要用 tsconfig.json 控制整个项目的编译规则(比如编译后的 JS 放哪里、兼容哪个 JS 版本)。

第一步:生成配置文件

在项目根目录(比如 my-ts-project)打开命令行,输入:

tsc --init

会自动生成 tsconfig.json 文件,里面有很多注释,我们只需要改几个核心配置。

第二步:改核心配置(新手必改 5 项)

打开 tsconfig.json,找到 compilerOptions 里的这些配置,改成下面这样:

{
   "compilerOptions": {
      "target": "ES6", // 编译后的 JS 版本(ES6 兼容性好,大部分浏览器都支持)
      "module": "ESNext", // 模块规范(跟前端框架配合用 ESNext 就行)
      "outDir": "./dist", // 编译后的 JS 文件放 dist 文件夹(方便管理)
      "rootDir": "./src", // 你的 TS 源码放 src 文件夹(规范目录结构)
      "strict": true,  // 开启严格模式(必开!强制检查类型,避免隐性 Bug)

      // 下面这几个默认就行,不用改
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true
   },
   "include": ["./src/**/*"], // 要编译的文件:src 下所有子文件夹的 .ts 文件
   "exclude": ["node_modules"] // 不用编译的文件:依赖包(装的第三方库)
}

简单说:你把 TS 代码写在 src 里,编译后会自动生成到 dist 里,项目运行时用 dist 里的 JS 文件。

第三步:用配置文件编译

在项目根目录输入 tsc(不用加文件名),TS 会自动按照 tsconfig.json 的规则,把 src 里所有 TS 文件编译成 JS 放到 dist 里。

三、TypeScript 核心语法:给变量 “贴标签”—— 基础类型系统

TS 的核心就是 “给变量定类型”,比如 “这个变量是数字”“那个函数返回字符串”。下面讲最常用的类型,每个都带 “实际用法” 和 “避坑提示”。

1. 先搞懂:类型声明 vs 自动推断

新手不用每次都手动写类型,TS 会 “猜” 类型,这两种方式都能用:

  • 手动声明:明确告诉 TS 变量类型,语法 let 变量名: 类型 = 值;

  • 自动推断:TS 根据你给的初始值,自动猜类型(推荐优先用这个,少写冗余代码)

举个例子:

// 手动声明:告诉 TS age 是数字
let age: number = 18;

// 自动推断:TS 看到初始值是 18,就知道 age2 是数字
let age2 = 18;

// 两种方式效果一样,后面改值时都不能传非数字
age = 20; // 对

// age = "20"; // 错:string 不能赋值给 number

2. 常用基础类型:每个类型都带 “生活例子”

类型例子通俗解释(什么时候用)避坑提示
number18, -5, 3.14, 100n所有数字(年龄、价格、分数),100n 是超大数字(比如订单号)别用 new Number(18)(会生成对象,不是纯数字,容易出问题)
string"张三", 'hello', `年龄${age}`字符串(名字、地址、文本),` ` 是模板字符串,能插变量模板字符串里插变量用 ${},比如 `我今年${age}岁`
booleantrue, false布尔值(开关状态、是否选中)别写 let isDone: boolean = new Boolean(false)(同上,生成对象)
字面量类型'男', '女', 1, 2限定变量只能取某个值(比如性别只能是 “男” 或 “女”)常和(联合类型)一起用,比如 type Gender = ’ 男 ’ | ’ 女 ’
anylet x: any = 1; x = "a";任意类型(关闭 TS 检查,相当于写 JS)尽量少用!用多了就失去 TS 的意义,替代方案是 unknown
unknownlet x: unknown = 1;不知道类型(但比 any 安全,用之前要确认类型)不能直接用,比如 x.toUpperCase() 会报错,要先判断类型
voidfunction fn(): void {}无返回值(函数不返回东西,或只写 return;)主要用在函数返回值,变量用 void 只能赋值 undefined
array[1,2,3], ["a","b"]数组(列表数据,比如学生列表、商品列表)两种写法:number[](数字数组)或 Array<number>(效果一样)
tuple['张三', 18]固定长度和类型的数组(比如 “名字 + 年龄” 的组合,不能多也不能少)越界访问会警告,比如 let user: [string, number] = ['张三', 18]; user[2] = 20 会报错
enumenum Status { 待支付, 已支付 }命名常量(比如订单状态、按钮状态,不用记数字或字符串)推荐用字符串枚举,比如 enum Status { 待支付 = 'PENDING', 已支付 = 'PAID' },语义更清晰

3. 重点类型深讲:新手最容易懵的 3 个类型

(1)any vs unknown:为什么说 any 是 “毒苹果”?

any 就像 “不管不顾”,写的时候爽,但运行时容易崩;unknown 是 “小心谨慎”,用之前要确认身份。

举个实际场景:后端返回的数据,不知道是什么类型。

// 用 any:危险!
let res: any = await axios.get("/api/user");

// 不管 res.data 是什么类型,都能写 res.data.name,但如果后端返回的是数字,运行时就报错
console.log(res.data.name);

// 用 unknown:安全!
let res: unknown = await axios.get("/api/user");

// 先判断类型,再用
if (typeof res === "object" && res !== null && "data" in res) {
 const user = res.data as { name: string }; // 确认是用户对象
 console.log(user.name); // 安全
}

简单说:不确定类型时,用 unknown 而不是 any,多写一行判断,少一个线上 Bug。

(2)tuple 元组:固定结构的数组

元组适合存 “结构固定” 的数据,比如地图上的坐标(x 轴 + y 轴)、键值对(key + value)。

例子:用元组存坐标和用户信息

// 坐标:第一个是 x 轴(数字),第二个是 y 轴(数字)
type Point = [number, number]
const pos: Point = [100, 200] // 正确
// const pos: Point = [100, "200"]; // 错误:第二个必须是数字

// 用户信息:名字(字符串)+ 年龄(数字)+ 是否成年(布尔)
type UserInfo = [string, number, boolean]
const user: UserInfo = ["张三", 18, true] // 正确
// const user: UserInfo = ["张三", 18]; // 错误:少一个布尔值

(3)enum 枚举:不用记 “魔法值”,代码更易读

“魔法值” 就是没注释的数字或字符串,比如 if (order.status === 1),别人根本不知道 1 代表什么。用枚举就能解决这个问题。

例子:订单状态管理

// 定义枚举:给每个状态起名字
enum OrderStatus {
   待支付 = "PENDING",
   已支付 = "PAID",
   已发货 = "SHIPPED",
   已完成 = "COMPLETED"
}

// 使用:不用记字符串,直接用枚举名
const order = {
   id: 1,
   status: OrderStatus.待支付 // 语义清晰
}

// 判断状态:不用写 "PENDING",不容易写错
if (order.status === OrderStatus.待支付) {
   console.log("请尽快支付")
}

简单说:枚举就是给 “固定值” 起个好记的名字,让代码不用猜。

4. 类型断言:告诉 TS“我比你懂,听我的”

有时候你知道变量的类型,但 TS 不知道,这时候就用 “类型断言”—— 相当于 “拍胸脯保证”:“这个变量就是这个类型,出问题我负责”。

语法有两种,推荐用第一种(和 JSX 兼容):

  • 值 as 类型(推荐)

  • <类型>值(不推荐,和 React 等框架的标签冲突)

实际场景:获取 DOM 元素(TS 不知道你选的是 div 还是 span)

// 1. 获取 id 为 "app" 的元素,TS 只知道是 HTMLElement(通用元素类型)
const app = document.getElementById("app");

// 2. 你知道这是 div 元素,用断言告诉 TS
const appDiv = app as HTMLDivElement;

// 3. 现在可以安全访问 div 特有的属性(比如 style)
appDiv.style.color = "red";

// 避坑:如果 app 是 null(没找到元素),会报错,所以最好加判断
if (app) {
   const appDiv = app as HTMLDivElement;
   appDiv.style.color = "red";
}

四、TypeScript 面向对象:用 “类” 组织代码 —— 适合大型项目

面向对象就是 “把相关的属性和方法打包成一个类”,比如 “人” 有名字、年龄(属性),会说话、走路(方法),用 class 就能定义一个 “人类”。

1. 类(Class):创建对象的 “模板”

比如定义一个 “人类”,用这个模板创建 “张三”“李四” 等具体的人。

// 定义“人类”模板
class Person {
  // 简化写法:构造函数参数加 public,自动声明并赋值属性(不用单独写 name: string; age: number;)
  constructor(public name: string, public age: number) { }

  // 方法:人会说话
  sayHello (): void {
    console.log(`大家好,我是 ${this.name},今年 ${this.age} 岁`)
  }

  // 静态方法:属于“人类”这个模板,不是某个具体的人(不用创建实例就能用)
  static isAdult (age: number): boolean {
    return age >= 18 // 判断是否成年
  }
}

// 用模板创建“张三”这个具体的人(实例化)
const zhangsan = new Person("张三", 20)

zhangsan.sayHello() // 输出:大家好,我是 张三,今年 20 岁

// 调用静态方法:直接用类名调用,不用创建实例
console.log(Person.isAdult(20)) // 输出:true(20岁成年)

简单说:class 是模板,new Class() 是具体的对象,静态方法是 “模板的工具”,不用造对象就能用。

2. 封装:给属性 “加锁”,不让随便改

比如 “人” 的年龄,不能随便改(比如改成 200 岁),这时候就把年龄设为 “私有”,只能通过指定方法修改。

class Person {
  // 私有属性:加 private,只能在类内部访问,外部不能直接改
  private _age: number

  constructor(public name: string, age: number) {
    this._age = age // 初始化年龄,只能在类内部赋值
  }

  // getter:获取年龄(对外提供“读”的权限)
  get age (): number {
    return this._age
  }

  // setter:修改年龄(对外提供“写”的权限,还能加验证)
  set age (newAge: number) {
    if (newAge < 0 || newAge > 150) { // 年龄不能小于0或大于150
      throw new Error("年龄必须在 0-150 之间!")
    }
    this._age = newAge
  }
}

// 使用
const lisi = new Person("李四", 25)
console.log(lisi.age) // 输出:25(调用 getter)
lisi.age = 30 // 正确(调用 setter,30 在 0-150 之间)
// lisi.age = 200; // 报错:年龄必须在 0-150 之间!
// lisi._age = 30; // 报错:_age 是 private,外部不能访问

实际用途:比如用户的余额、积分等敏感数据,不能让外部随便改,只能通过 “充值”“消费” 等方法修改,就用这种方式。

3. 继承:少写重复代码

比如 “动物” 有名字、会吃东西,“狗” 和 “猫” 继承 “动物”,不用重复写名字和吃的方法,只需要加自己的特色(狗会叫,猫会喵喵叫)。

// 父类:动物(通用属性和方法)
class Animal {
  constructor(public name: string) { }

  // 动物都会吃东西
  eat (): void {
    console.log(`${this.name} 在吃东西`)
  }
}

// 子类:狗(继承动物,加自己的方法)
class Dog extends Animal {
  // 子类构造函数必须调用 super(),把名字传给父类
  constructor(name: string, public breed: string) { // breed:品种
    super(name) // 调用父类的构造函数,传名字
  }

  // 重写父类方法:狗吃东西有自己的特点
  eat (): void {
    super.eat() // 先调用父类的 eat 方法
    console.log(`${this.name}(${this.breed})喜欢吃骨头`)
  }

  // 子类新增方法:狗会汪汪叫
  bark (): void {
    console.log(`${this.name} 在汪汪叫`)
  }
}

// 用 Dog 类创建“旺财”
const wangcai = new Dog("旺财", "金毛")
wangcai.eat() // 输出:旺财 在吃东西 → 旺财(金毛)喜欢吃骨头
wangcai.bark() // 输出:旺财 在汪汪叫

简单说:继承就是 “拿别人的代码当基础,再加自己的东西”,减少重复代码。

4. 抽象类:不能直接用的 “模板的模板”

比如 “形状” 是抽象的,你不能说 “我画了一个形状”,只能说 “我画了一个圆形”“我画了一个正方形”。抽象类就是这样 —— 只能被继承,不能直接创建实例。

// 抽象类:形状(用 abstract 修饰)
abstract class Shape {
  constructor(public name: string) { }

  // 抽象方法:只有声明,没有实现(子类必须自己写实现)
  abstract calculateArea (): number // 计算面积

  // 普通方法:有实现,子类可以直接用
  printName (): void {
    console.log(`这是一个 ${this.name}`)
  }
}

// 子类:圆形(继承形状,必须实现 calculateArea)
class Circle extends Shape {
  constructor(name: string, public radius: number) { // radius:半径
    super(name)
  }

  // 实现抽象方法:计算圆形面积(πr²)
  calculateArea (): number {
    return Math.PI * this.radius * this.radius
  }
}

// 子类:正方形(继承形状,必须实现 calculateArea)
class Square extends Shape {
  constructor(name: string, public sideLength: number) { // sideLength:边长
    super(name)
  }

  // 实现抽象方法:计算正方形面积(边长²)
  calculateArea (): number {
    return this.sideLength * this.sideLength
  }
}

// 使用
const circle = new Circle("圆形", 5)
circle.printName() // 输出:这是一个 圆形
console.log(circle.calculateArea()) // 输出:78.539...(π*5²)
// const shape = new Shape("形状"); // 报错:抽象类不能直接创建实例

实际用途:定义 “通用规范”,比如所有组件都要有的 render 方法,就可以用抽象类约束。

5. 接口(Interface):定义 “结构契约”

接口和抽象类有点像,但更灵活 —— 它只定义 “有什么属性和方法”,不管怎么实现,还能让多个类遵守同一个规则。

场景 1:约束对象结构

// 定义接口:用户必须有 id(数字)和 name(字符串),age 可选
interface User {
  id: number
  name: string
  age?: number // 可选属性,可写可不写
  readonly email: string // 只读属性,初始化后不能改
}

// 用接口约束变量:必须符合 User 的结构
const user1: User = {
  id: 1,
  name: "张三",
  email: "zhangsan@example.com"
}

const user2: User = {
  id: 2,
  name: "李四",
  age: 20, // 可选属性,写了也没问题
  email: "lisi@example.com"
}

// user2.email = "new@example.com"; // 报错:email 是只读属性

场景 2:约束类的结构

// 定义接口:会飞的东西必须有 fly 方法
interface Flyable {
  fly (): void
}

// 定义接口:会游泳的东西必须有 swim 方法
interface Swimmable {
  swim (): void
}

// 类:鸭子(既会飞又会游泳,实现两个接口)
class Duck implements Flyable, Swimmable {
  // 必须实现 Flyable 的 fly 方法
  fly (): void {
    console.log("鸭子扇着翅膀飞")
  }

  // 必须实现 Swimmable 的 swim 方法
  swim (): void {
    console.log("鸭子在水里游")
  }
}

// 使用
const duck = new Duck()
duck.fly() // 输出:鸭子扇着翅膀飞
duck.swim() // 输出:鸭子在水里游

简单说:接口就是 “规则清单”,对象或类必须按清单来,保证结构统一。

五、TypeScript 泛型(Generic):写 “通用代码”

泛型就是 “类型参数化”—— 比如写一个 “获取数组第一个元素” 的函数,既能处理数字数组,又能处理字符串数组,不用写两个函数。

1. 泛型函数:一个函数适配多种类型

比如 “获取数组第一个元素”,支持数字、字符串、对象等各种数组。

// 泛型函数:<T> 是类型参数,代表“某种类型”(T 可以随便起,比如 <Type>)
function getFirstElement<T> (arr: T[]): T | undefined {
  return arr[0] // 返回值类型和数组元素类型一致
}

// 场景 1:处理数字数组
const numbers = [1, 2, 3]
const firstNum = getFirstElement(numbers)
// TS 自动推断 T 是 number,firstNum 类型是 number | undefined

// 场景 2:处理字符串数组
const strings = ["a", "b", "c"]
const firstStr = getFirstElement(strings)
// TS 自动推断 T 是 string,firstStr 类型是 string | undefined

// 场景 3:处理对象数组(比如 User 数组)
interface User { id: number; name: string }
const users: User[] = [{ id: 1, name: "张三" }, { id: 2, name: "李四" }]
const firstUser = getFirstElement(users)
// TS 自动推断 T 是 User,firstUser 类型是 User | undefined

实际用途:工具函数(比如深拷贝、数组处理)都用泛型,比如 JSON.parse 和 JSON.stringify 其实就是泛型函数。

2. 泛型类:一个类适配多种类型

比如 “本地存储工具类”,既能存用户信息,又能存商品列表,不用写多个存储类。

// 泛型类:<T> 是要存储的数据类型
class LocalStorage<T> {
  // 存数据:key 是字符串,value 是 T 类型
  setItem (key: string, value: T): void {
    localStorage.setItem(key, JSON.stringify(value))
  }

  // 取数据:返回 T 类型或 null
  getItem (key: string): T | null {
    const val = localStorage.getItem(key)
    return val ? JSON.parse(val) : null
  }
}

// 场景 1:存用户信息(T 是 User 类型)
interface User { id: number; name: string }
const userStorage = new LocalStorage<User>()
userStorage.setItem("user", { id: 1, name: "张三" }) // 只能存 User 类型
const user = userStorage.getItem("user") // 取出来还是 User 类型

// 场景 2:存商品列表(T 是 { id: number; name: string } 类型)
type Product = { id: number; name: string }
const productStorage = new LocalStorage<Product[]>()
productStorage.setItem("products", [{ id: 1, name: "手机" }, { id: 2, name: "电脑" }])
const products = productStorage.getItem("products") // 取出来是 Product[] 类型

3. 泛型约束:给泛型 “加限制”

有时候泛型不能太随意,比如写一个 “获取长度” 的函数,只能处理有 length 属性的类型(比如字符串、数组),不能处理数字。

// 1. 定义接口:有 length 属性的类型
interface HasLength {
  length: number
}

// 2. 泛型约束:T 必须是 HasLength 的子类型(必须有 length 属性)
function getLength<T extends HasLength> (item: T): number {
  return item.length
}

// 正确使用:字符串、数组都有 length 属性
console.log(getLength("hello")) // 5(字符串长度)
console.log(getLength([1, 2, 3])) // 3(数组长度)
// 错误使用:数字没有 length 属性
// console.log(getLength(123)); // 报错:number 没有 length 属性

简单说:泛型约束就是 “给通用代码加规则”,避免传错类型。

六、实战案例:用 TS 写一个完整的 TodoList(待办事项)

结合前面学的 “接口、类、泛型”,写一个能 “增删改查” 的 TodoList,覆盖实际开发中的核心逻辑。

1. 需求分析

需要实现的功能:

  • 添加待办事项

  • 切换待办状态(完成 / 未完成)

  • 编辑待办内容

  • 删除待办事项

  • 获取所有待办事项

2. 完整代码实现

// 1. 定义 Todo 类型(接口):每个待办有 id、内容、状态
interface Todo {
  id: number // 唯一 ID(用时间戳生成)
  content: string // 待办内容
  done: boolean // 完成状态:true 完成,false 未完成
  createTime: number // 创建时间(时间戳)
}

// 2. 实现 TodoList 类:封装所有功能
class TodoList {
  // 私有属性:存储待办列表,外部不能直接修改
  private todos: Todo[] = [];

  /**
   * 添加待办事项
   * @param content 待办内容
   */
  addTodo (content: string): void {
    if (!content.trim()) { // 内容不能为空
      throw new Error("待办内容不能为空!")
    }

    const newTodo: Todo = {
      id: Date.now(), // 用当前时间戳当唯一 ID
      content: content.trim(),
      done: false,
      createTime: Date.now()
    }

    this.todos.push(newTodo)
  }

  /**
   * 切换待办状态(完成 / 未完成)
   * @param id 待办 ID
   */
  toggleTodo (id: number): void {
    const todo = this.todos.find(t => t.id === id)

    if (!todo) { // 没找到对应 ID 的待办
      throw new Error(`未找到 ID 为 ${id} 的待办事项!`)
    }

    todo.done = !todo.done // 反转状态
  }

  /**
   * 编辑待办内容
   * @param id 待办 ID
   * @param newContent 新的待办内容
   */
  editTodo (id: number, newContent: string): void {
    if (!newContent.trim()) {
      throw new Error("待办内容不能为空!")
    }

    const todo = this.todos.find(t => t.id === id)

    if (!todo) {
      throw new Error(`未找到 ID 为 ${id} 的待办事项!`)
    }

    todo.content = newContent.trim()
  }

  /**
   * 删除待办事项
   * @param id 待办 ID
   */
  deleteTodo (id: number): void {
    const index = this.todos.findIndex(t => t.id === id)

    if (index === -1) {
      throw new Error(`未找到 ID 为 ${id} 的待办事项!`)
    }

    this.todos.splice(index, 1) // 从数组中删除
  }

  /**
   * 获取所有待办事项(返回副本,避免外部修改原数组)
   * @returns Todo[] 待办列表
   */
  getTodos (): Todo[] {
    return [...this.todos] // 用扩展运算符返回新数组
  }

  /**
   * 获取完成的待办事项
   * @returns Todo[] 完成的待办列表
   */
  getDoneTodos (): Todo[] {
    return this.todos.filter(t => t.done)
  }
}

// 3. 使用 TodoList
const myTodoList = new TodoList()

// 添加待办
myTodoList.addTodo("学习 TypeScript 基础")
myTodoList.addTodo("用 TS 写 TodoList")
myTodoList.addTodo("整理笔记")
console.log("初始待办列表:", myTodoList.getTodos())

/* 输出:
  [
    { id: 1698000000000, content: '学习 TypeScript 基础', done: false, createTime: 1698000000000 },
    { id: 1698000000001, content: '用 TS 写 TodoList', done: false, createTime: 1698000000001 },
    { id: 1698000000002, content: '整理笔记', done: false, createTime: 1698000000002 }
  ]
*/

// 切换第一个待办为完成
const firstTodoId = myTodoList.getTodos()[0].id
myTodoList.toggleTodo(firstTodoId)
console.log("完成的待办:", myTodoList.getDoneTodos()) // 输出第一个待办

// 编辑第二个待办
const secondTodoId = myTodoList.getTodos()[1].id
myTodoList.editTodo(secondTodoId, "用 TS 写一个完整的 TodoList")
console.log("编辑后的待办列表:", myTodoList.getTodos()[1].content) // 输出新内容

// 删除第三个待办
const thirdTodoId = myTodoList.getTodos()[2].id
myTodoList.deleteTodo(thirdTodoId)
console.log("删除后的待办数量:", myTodoList.getTodos().length) // 输出 2

七、新手避坑指南:10 个最常见的 TS 错误及解决方案

1. 引入第三方库时提示 “找不到模块 ‘axios’”?

原因:TS 不知道 axios 的类型(需要 .d.ts 类型文件)。

解决:安装 axios 的类型包(大部分库的类型包都在 @types 下):

npm install @types/axios --save-dev

2. 函数参数可选,但使用时提示 “可能为 undefined”?

例子:

// 可选参数 name 可能是 undefined
function greet(name?: string) {
   console.log(`Hello ${name.toUpperCase()}`); // 报错:name 可能为 undefined
}

解决:加默认值或判断:

// 方案 1:加默认值
function greet (name: string = "Guest") {
  console.log(`Hello ${name.toUpperCase()}`)
}

// 方案 2:判断是否存在
function greet (name?: string) {
  if (name) { // 存在才执行
    console.log(`Hello ${name.toUpperCase()}`)
  } else {
    console.log("Hello Guest")
  }
}

3. 类型 “string” 不能赋值给类型 “number”,但实际是数字字符串?

例子:

const ageStr = "18"; // 数字字符串
const age: number = ageStr; // 报错:string 不能赋值给 number

解决:用 Number() 或 parseInt() 转换类型:

const age: number = Number(ageStr); // 正确,18 是数字
// 或
const age: number = parseInt(ageStr, 10); // 正确,10 是进制

4. typeof x === 'object' 为什么会包含 null?

原因:JS 历史 Bug,typeof null === 'object',TS 继承了这个特性。

例子:

function isObject(x: unknown): x is object {
   return typeof x === "object"; // 错误:x 为 null 时也会返回 true
}

解决:额外判断 x !== null:

function isObject(x: unknown): x is object {
   return typeof x === "object" && x !== null;
}

5. 泛型函数中无法访问属性 “name”?

例子:

// 想访问 obj.name,但 TS 不知道 obj 有没有 name
function printName<T>(obj: T) {
   console.log(obj.name); // 报错:T 上不存在属性 name
}

解决:用泛型约束,限制 T 必须有 name 属性:

interface HasName {
   name: string;
}

function printName<T extends HasName>(obj: T) {
   console.log(obj.name); // 正确,T 一定有 name
}

6. 数组遍历后,元素类型还是 “unknown”?

例子:

const arr: unknown[] = [1, "2", 3]

arr.forEach(item => {
  console.log(item + 1) // 报错:item 是 unknown,不能直接运算
})

解决:遍历中判断类型:

arr.forEach(item => {
  if (typeof item === "number") {
    console.log(item + 1) // 正确,item 是数字
  } else if (typeof item === "string") {
    console.log(Number(item) + 1) // 正确,转成数字再运算
  }
})

7. 类型 “{}” 上不存在属性 “id”?

例子:

const user = {}; // TS 推断为 {}(空对象)
user.id = 1; // 报错:{} 上不存在属性 id

解决:提前定义类型或用类型断言:

// 方案 1:提前定义类型
interface User { id?: number }
const user: User = {}
user.id = 1 // 正确

// 方案 2:类型断言
const user = {} as User
user.id = 1 // 正确

8. enum 枚举值是数字,想转成字符串报错?

例子:

enum Status { 待支付, 已支付 } // 默认是数字枚举,Status.待支付 = 0
const statusStr: string = Status.待支付; // 报错:number 不能赋值给 string

解决:用字符串枚举,或用 String() 转换:

// 方案 1:字符串枚举(推荐)
enum Status {
  待支付 = "PENDING",
  已支付 = "PAID"
}
const statusStr: string = Status.待支付 // 正确,"PENDING" 是字符串

// 方案 2:转换数字枚举
const statusStr: string = String(Status.待支付) // 正确,"0" 是字符串

9. 函数返回值类型不匹配,比如 “应该返回 number 却返回了 string”?

例子:

function add(a: number, b: number): number {
   return `${a + b}`; // 报错:返回的是 string,不是 number
}

解决:要么改返回值类型,要么改返回的内容:

// 方案 1:改返回值类型为 string
function add (a: number, b: number): string {
  return `${a + b}` // 正确
}

// 方案 2:改返回内容为 number
function add (a: number, b: number): number {
  return a + b // 正确
}

10. tsc 编译后,dist 文件夹没生成?

原因:tsconfig.json 的 rootDir 配置错误,找不到源码文件。

解决:确认 rootDir 是源码所在目录(比如 src),且 src 下有 .ts 文件,然后重新执行 tsc。

【TypeScript】一文带你了解什么是TypeScript TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。TypeScript是一种非常受欢迎的JavaScript语言扩展。它在现有的JavaScript语法之上加入了一层类型层,而这一层即使被删除,也丝毫不会影响运行时的原有表现。许多人认为TypeScript“只是一个编译器”,但更好的理解其实是把TypeScript编译器(即处理语法的部分)和语言工具(即处理与编辑器集成的部分)。.......... 阅读详情

相关推荐

【TypeScript】深入学习TypeScript函数

博主的TypeScript专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。

前端之行,任重道远! 9539

TypeScript:熟练掌握TypeScript

TypeScript核心概念与优势摘要 TypeScript是JavaScript的超集,扩展了JS的静态类型系统,主要优势包括: 静态类型检查:编译阶段即可发现类型错误,避免运行时异常 增强开发体验:完善的代码提示和自动补全功能 面向对象支持:完整支持类、接口、泛型等特性 渐进式采用:兼容现有JS代码,可逐步迁移 框架友好:Vue3/Angular/React等主流框架深度集成 核心特性: 类型注解与接口定义 联合类型与类型别名 泛型编程支持 枚举与元组类型 类修饰符(public/private等) 适

snow的博客 9519

WebRTC音频传输实战:从采集到播放的完整链路解析(附RTP包处理细节)

本文深入解析了WebRTC音频传输的完整链路,从麦克风采集、音频处理与编码,到RTP打包、网络发送,再到接收端的NetEQ抗抖动处理与最终播放。文章以Linux PulseAudio为例,详细剖析了线程模型、缓冲区管理及RTP包处理等关键细节,旨在帮助开发者优化实时音频应用的延迟、卡顿与回声问题。

tea88的博客 851

【TypeScript】TypeScript

TypeScript 是什么?TypeScript为什么要为JS添加类型支持?TypeScript相比JS的优势。

shisanxiang_的博客 3839

因果推断综述-A Survey on Causal Inference

最近读到一篇讲述很全面的综述文献-《A Survey on Causal Inference》,对于接触因果推断不久的同学而言是特别详细的介绍和科普。文献很长,我会分成几部分介绍。 目录 摘要 第一部分:简介 第二部分:因果推断基础知识 第三部分:基于三种假设的因果推断方法 第四部分:实验 第五部分:应用 摘要 几十年来,因果推理是一个跨许多领域的关键研究主题,如统计、计算机科学、教育、公共政策和经济学。与随机对照试验相比,研究方向由于可用数据量大且预算要求低,如今,从观测数据..

海边凝望的博客 3182

Keil MDK编译错误#541解决方案与ARM_Compiler包解析

在嵌入式开发中,编译器工具链的依赖管理是确保项目顺利构建的关键环节。以Keil MDK为例,其软件包依赖机制通过组件化设计实现功能解耦,其中ARM_Compiler包专门处理系统IO重定向等编译器扩展功能。这种架构既提升了模块复用性,也带来了版本兼容性挑战。当出现'Error #541: component missing'报错时,通常是由于中间件组件(如文件系统)在新版本中显式依赖了ARM_Compiler包。开发者需要通过Pack Installer安装该包,并在Run-Time Environment

weixin_32129187的博客 365

TypeScript起航篇·何为TypeScript?

添加了类型系统的JavaScript,适用于任何规模的项目。强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何项目。TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。TypeScript 是一门静态类型、弱类型的语言。TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。

安然无虞的博客 1490

深度解析:C# Modbus TCP协议解析与异常处理(工控上位机必备)

协议解析要精准:掌握MBAP头和PDU的结构,尤其是常用功能码的请求/响应格式,避免因字节处理错误导致通信失败;异常处理要全面:覆盖通信层、协议层、数据层的所有异常类型,针对工业场景制定重试、重连、滤波等策略;资源管理要规范:使用释放资源,采用连接池复用连接,避免端口泄露;工业场景要适配:结合工控现场的特点(网络波动、电磁干扰、设备忙),优化通信效率和数据处理逻辑。通过以上解析和策略,可确保C#上位机与工业设备的Modbus TCP通信具备高稳定性、高可靠性、高容错性,满足工控领域的严苛要求。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 997

[TypeScript]TypeScript语法

目录什么是TypeScript优点使用安装语法数据类型面向对象类泛型接口模块注解类型定义 什么是TypeScript 微软开发的编程语言,一种客户端脚本语言(一般是不经过数据库,不经过其他编译软件编译,直接写在网站代码里的) TS是JS的超级,遵循ES6规范。 TS向JS添加遵循ES6的规范的语法,以及基于类面向对象编程特性 TS可以用来写google公司开发的Angular框架 优点 提升代码质量,增强代码可读性和可维护(IDE 类型检查,语法检查,重构) 可以用来开发Angular 使用 安装 np

stay hungry stay foolish 2988

MIKE水动力笔记1_岸线及水深数据之依靠全球数据库资源提取的方法

通过各种数据库资源来获取全球岸线及水深数据,这种方法获取的岸线及水深数据精度偏低,比较适合大尺度范围的水动力研究。

晏长街的博客 1万+

Power domain 划分该如何下手

假如PD1比PD3更always on的话,我们只需要插入普通buffer即可,因为PD3 on,PD1也肯定是ON,PD1 off,PD3肯定时off,PD1能cover PD3的电源情况,因此使用普通buffer也可,但是处于省电角度,我们还是使用AO buffer;在我们做多power domain时,在切分power domain时,需要谨慎,稍有不慎,就会出现congestion以及timing问题,那问题来了,我们在切分power domain时有什么考究么,今天小编就和大家一起讨论讨论。

芯冰乐 207

npm教程:如何查看TypeScript版本、卸载TypeScript和安装TypeScript

npm教程:如何查看TypeScript版本、卸载TypeScript和安装TypeScript TypeScript是JavaScript的一个超集,也可以编译成JavaScript;适用任何浏览器、服务器、操作系统,并且是开源免费的。 如果本机已经安装了TypeScript,如何查看对应的版本?如果安装的是旧版本,需要卸载,该怎么卸载TypeScript?卸载完了,如何进行安装TypeScript? 精简版 1、查看本机TypeScript版本,可以使用npm view typescript versi

学亮编程手记 1万+

STM32F407+OV2640的DCMI+DMA双缓冲JPEG图像采集系统

在嵌入式视觉系统中,数字摄像头接口(DCMI)是MCU连接CMOS图像传感器的关键硬件通道,其与DMA协同实现零CPU干预的高速数据流采集;而JPEG作为一种基于DCT变换与量化压缩的有损图像格式,依赖传感器端硬件编码引擎才能满足实时性要求。理解DCMI三重同步时序(PCLK/HSYNC/VSYNC)与DMA双缓冲机制的流水线协作原理,是突破带宽瓶颈、避免FIFO溢出和帧丢失的技术基础。该架构广泛应用于智能硬件、工业检测与边缘AI前端等需低功耗、高可靠图像捕获的场景,本文以OV2640与STM32F407为

weixin_42103128的博客 156

TypeScript 这十年

【优快云 编者按】很多时候,仅从名称上来看,不少人对 TypeScript 与 JavaScript 傻傻分不清楚,或许只知道 TypeScript 作为 JavaScript 的后来者,想要将其取而代之,却时至今日未能如愿。殊不知,TypeScript 诞生落地发展到当下,已有十年的时间。虽然未能达到 JavaScript 流行的高度,但也弥补了其不少不足之处。近日,微软 TypeScript...

优快云资讯 1万+

ABAP BDC 处理踩坑笔记:把错误挡在录制之前,把稳定做进每一行 BDCDATA

本文总结了SAP BDC(批输入)开发中的常见问题和解决方案。重点包括:1)理解BDCDATA本质是模拟人工操作而非直接操作数据库;2)数据格式化技巧,如金额、日期、物料号等字段的标准化处理;3)通过CTU_PARAMS参数控制执行环境,确保屏幕尺寸和更新模式一致;4)不要依赖sy-subrc,应通过解析MESSTAB消息判断业务是否成功;5)录制阶段的常见陷阱及解决方法;6)疑难问题的系统性解决思路;7)建议建立完善的错误处理机制,包括日志记录和重跑功能。文章提供了实用的ABAP代码示例和配置建议,帮助开

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 265

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

devenv 中配置 TypeScript 开发环境:languages.typescript 模块完全指南

`languages.typescript` 是 devenv 内置的语言模块之一,用于在基于 Nix 的可复现开发环境中一键启用 TypeScript 工具链:只需一个布尔开关即可把 `typescript` 编译器与 TypeScript Language Server(`typescript-language-server`)加入开发环境。读完本文,你将掌握该模块的全部配置选项、默认值与类型

gitblog_01110的博客 884

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

TypeScript ESLint Parser快速上手:5分钟完成TypeScript代码检查配置

TypeScript ESLint Parser是一款强大的ESLint自定义解析器,它利用TypeScript ESTree让ESLint能够检查TypeScript源代码。本文将为你提供一个简单快速的配置指南,帮助你在5分钟内完成TypeScript代码检查环境的搭建。 ## 📦 1. 安装核心依赖 首先,你需要安装TypeScript ESLint Parser及其相关依赖。打开终端,

gitblog_00846的博客 1029

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

为什么TypeScript ESLint Parser是TypeScript项目必备工具?核心功能解析

TypeScript ESLint Parser是一个允许ESLint检查TypeScript源代码的自定义解析器,它通过利用TypeScript ESTree实现TypeScript代码与ESLint的无缝集成,为TypeScript项目提供了强大的代码质量保障能力。 ## 一、TypeScript与ESLint的桥梁:核心功能解析 TypeScript ESLint Parser的核心价值

gitblog_00886的博客 879

【TypeScript】从零开始玩转typescript - typescript入门

前言 小伙伴们大家好。从今天开始我们将从零开始系统的学习一些TypeScript相关的知识。相信小伙伴们或多或少应该也都听说过这玩意了,那么这家伙是干嘛的呢,已经有JavaScript了为什么又要搞出个TypeScript,接下来就为大家一一揭晓。 TypeScript TypeScript是个啥,它跟JavaScript又有啥区别? TypeScript是由微软团队开发的一种开源、自由的编程语言。 TypeScript是JavaScript的一个超集,支持ES6的所有标准。 TypeScript是

lixiaosenlin的专栏 2253

TypeScript入门

TypeScript 是一种由微软开发的自由和开源的编程语言,它是 JavaScript 的一个超集,扩展了 JavaScript 的语法。

★【World Of Moshow 郑锴】★ 3831

TypeScript 安装

很多 IDE 都有支持 TypeScript 插件,如:Visual Studio,Sublime Text 2,WebStorm / PHPStorm,Eclipse 等。本章节主要介绍 Visual Studio Code,Visual Studio Code 是一个可以运行于 Mac OS X、Windows 和 Linux 之上的,针对于编写现代 Web 和云应用的跨平台源代码编辑器,由 Microsoft 公司开发。。

li514006030的博客 1731

如何查看typescript版本,安装指定typescript版本,卸载typescript

WARNING: You are currently running a version of TypeScript which is not officially supported by typescript-estree. You may find that it works just fine, or you may not. SUPPORTED TYPESCRIPT VERSIONS: >=3.2.1 <3.6.0 YOUR TYPESCRIPT VERSION: 3.9....

q1ngqingsky的博客 5499

typescript环境安装及IDEA配置typescript

一、typescript环境安装 1、安装node、npm 下载官网安装包: http://nodejs.cn/download/, 双击运行 2、安装完node、npm后,查看是否安装成功 node -v npm -v 3、安装typescript npm install -g typescript 4、安装后查看typescript是否安装成功 tsc -version 或者 ts...

千里之行,始于足下! 9867

Typescript详解

什么是TypeScipt? 参考文档官方文档 TypeScript 是由微软开发的一款开源的编程语言。 TypeScript 是 Javascript 的超集,遵循最新的ES6、Es5 规范。TypeScript 扩展了 JavaScript 的语法。 TypeScript 更像后端java、C#这样的面向对象语言,可以让 js 开发大型企业项目。 谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+就是基于 Typescript 语法。 最新的 Vue 、React 也可以集成

liujiawei567的博客 4614
上一篇: axios
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值