快速学习Typescript

117 阅读11分钟

一、学习前准备(认知 + 环境)

1.1 为什么学 TS?(解决 “学了有啥用” 的问题)

核心价值解析

  • JS 是弱类型语言,运行时才暴露类型错误(如传参类型错、属性不存在),TS 是 JS 的超集,增加静态类型检查,编译期发现错误;
  • 提升代码可读性:类型标注让函数 / 变量用途一目了然,团队协作成本降低;
  • 增强 IDE 提示:VS Code 等工具基于 TS 提供精准补全、重构;
  • 前端主流框架标配:Vue3、React 均推荐使用 TS 开发,大厂面试必备。

举例说明

javascript

运行

// JS 问题:运行时才发现错误
function add(a, b) {
  return a + b;
}
add(1, '2'); // 返回 "12",而非预期的 3,运行时才发现参数类型错

// TS 解决:编译期报错
function add(a: number, b: number): number {
  return a + b;
}
add(1, '2'); // 编译期直接报错:类型“string”的参数不能赋给类型“number”的参数

1.2 环境搭建(动手落地)

核心步骤讲解

  1. 安装 Node.js(v14+):TS 编译依赖 Node 环境,官网 nodejs.org/ 下载;

  2. 全局安装 TS:npm install -g typescript,验证:tsc -v(显示版本则成功);

  3. 配置编辑器:推荐 VS Code(内置 TS 支持),安装插件:

    • TypeScript Hero:增强 TS 语法提示;
    • ESLint:规范 TS 代码;
  4. 初始化第一个 TS 项目:

    bash

    运行

    mkdir ts-demo && cd ts-demo
    npm init -y # 生成 package.json
    tsc --init # 生成 tsconfig.json(TS 核心配置文件)
    

配置解析(tsconfig.json 核心字段)

字段作用示例
target编译后的 JS 版本"target": "ES6"(编译为 ES6 代码)
module模块系统"module": "ESNext"(使用 ES 模块)
outDir编译后 JS 文件输出目录"outDir": "./dist"
rootDir源文件目录"rootDir": "./src"
strict开启严格模式(必开!)"strict": true(强制类型检查)

实操示例

  1. 在 src 目录新建 index.ts

    typescript

    运行

    let name: string = 'TS 学习';
    console.log(name);
    
  2. 编译 TS:tsc(自动读取 tsconfig.json,生成 dist/index.js);

  3. 运行 JS:node dist/index.js(输出 “TS 学习”)。


二、TS 核心基础

2.1 基本类型(TS 最核心的 “类型标注”)

核心概念讲解

TS 为 JS 原始类型增加了类型标注,核心基本类型包括:numberstringbooleannullundefinedsymbolbigint,以及特殊类型 voidanynever

逐类型解析 + 举例

类型说明正确示例错误示例
number数字(整数 / 浮点数)let age: number = 18;let age: number = '18';
string字符串let name: string = ' 张三 ';let name: string = 123;
boolean布尔值let isAdult: boolean = true;let isAdult: boolean = 1;
null/undefined空值 / 未定义let n: null = null;let n: null = undefined;
void无返回值(函数)function log(): void { console.log('hi'); }function log(): void { return 1; }
any任意类型(关闭 TS 检查)let val: any = 1; val = 'str';-(无错误,但尽量不用)
never永远不会返回(报错 / 死循环)function error (): never { throw new Error (' 错了 '); }function fn(): never { return 1; }

关键注意点

  • strict 模式下,null 和 undefined 不可以赋值给其他基本类型(如 let a: number = null 会报错);
  • any 是 TS 的 “逃逸舱”,使用后 TS 失去类型检查意义,新手尽量避免;
  • never 常用于错误处理函数、无限循环函数。

2.2 变量与常量的类型标注

核心语法讲解

  • 变量:let 变量名: 类型 = 值;
  • 常量:const 常量名: 类型 = 值;(常量类型可省略,TS 自动推导)

举例说明

typescript

运行

// 显式标注
let score: number = 90;
let username: string = '李四';

// 类型推导(TS 自动识别,推荐)
let height = 180; // 自动推导为 number
const gender = '男'; // 自动推导为 '男'(字面量类型)

// 错误示例
let weight: number = '60kg'; // 报错:类型不匹配

2.3 函数的类型标注(参数 + 返回值)

核心语法讲解

typescript

运行

// 函数声明
function 函数名(参数1: 类型1, 参数2: 类型2): 返回值类型 {
  // 逻辑
}

// 函数表达式
const 函数名 = (参数1: 类型1, 参数2: 类型2): 返回值类型 => {
  // 逻辑
};

场景解析 + 举例

场景 1:基础函数标注

typescript

运行

// 加法函数:两个数字参数,返回数字
function add(a: number, b: number): number {
  return a + b;
}
add(1, 2); // ✅
add(1, '2'); // ❌ 参数类型错
场景 2:可选参数(加?)

typescript

运行

// 可选参数必须放在必选参数后面
function getUser(name: string, age?: number): string {
  return age ? `${name}-${age}` : name;
}
getUser('张三'); // ✅
getUser('张三', 18); // ✅
场景 3:默认参数

typescript

运行

// 默认参数无需标注类型(TS 自动推导)
function request(url: string, timeout = 1000): void {
  console.log(`请求${url},超时${timeout}ms`);
}
request('/api'); // ✅ timeout 默认为 1000
场景 4:剩余参数

typescript

运行

function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // ✅ 返回 6
sum(1, '2'); // ❌ 剩余参数必须是 number

2.4 数组与元组

数组讲解 + 举例

  • 语法 1:let 数组名: 类型[] = [值1, 值2];
  • 语法 2:let 数组名: Array<类型> = [值1, 值2];(泛型写法,后续讲)

typescript

运行

// 数字数组
let nums: number[] = [1, 2, 3];
nums.push(4); // ✅
nums.push('5'); // ❌

// 字符串数组
let strs: string[] = ['a', 'b'];

// 任意类型数组(不推荐)
let arr: any[] = [1, 'a', true];

元组讲解 + 举例

元组是 “固定长度 + 固定类型” 的数组,适用于明确元素数量和类型的场景。

typescript

运行

// 定义元组:第一个元素 string,第二个 number
let user: [string, number] = ['张三', 18];
user[0] = '李四'; // ✅
user[1] = 20; // ✅
user.push(21); // ✅(TS 允许追加,但访问 user[2] 会报错)
user[0] = 123; // ❌ 类型不匹配

2.5 接口(Interface)- 描述对象形状

核心概念讲解

接口是 TS 中描述对象 / 函数类型的核心工具,用于定义 “对象应该有哪些属性,属性是什么类型”,支持可选属性、只读属性、索引签名。

语法 + 场景举例

场景 1:基础对象接口

typescript

运行

// 定义接口
interface Person {
  name: string; // 必选属性
  age: number; // 必选属性
  gender?: string; // 可选属性
  readonly id: number; // 只读属性(不可修改)
}

// 使用接口
const p: Person = {
  name: '王五',
  age: 20,
  id: 1001 // 只读
};
p.gender = '男'; // ✅ 可选属性可赋值
p.id = 1002; // ❌ 只读属性不可修改
p.address = '北京'; // ❌ 接口未定义 address 属性
场景 2:索引签名(处理未知属性)

适用于对象属性数量不确定,但键值类型明确的场景。

typescript

运行

interface Obj {
  [key: string]: number; // 键是 string,值是 number
}

const obj: Obj = {
  a: 1,
  b: 2
};
obj.c = 3; // ✅
obj.d = '4'; // ❌ 值必须是 number
场景 3:接口继承(复用类型)

typescript

运行

// 基础接口
interface Animal {
  name: string;
  eat(): void;
}

// 继承 Animal
interface Dog extends Animal {
  bark(): void; // 新增方法
}

const dog: Dog = {
  name: '旺财',
  eat() { console.log('吃骨头'); },
  bark() { console.log('汪汪汪'); }
};

2.6 类型别名(Type Alias)

核心概念讲解

类型别名用 type 定义,可给任意类型起别名,功能类似接口,但更灵活(支持联合类型、交叉类型等)。

语法 + 对比接口 + 举例

typescript

运行

// 基础类型别名
type Score = number;
let math: Score = 95; // 等价于 let math: number = 95;

// 对象类型别名
type User = {
  name: string;
  age: number;
};
const u: User = { name: '赵六', age: 22 };

// 联合类型别名(接口不支持)
type Status = 'success' | 'error' | 'loading';
let resStatus: Status = 'success'; // ✅
resStatus = 'fail'; // ❌ 不在联合类型中

// 交叉类型别名(合并多个类型)
type A = { a: number };
type B = { b: string };
type C = A & B; // 同时有 a 和 b 属性
const c: C = { a: 1, b: 'hello' };

接口 vs 类型别名(关键区别)

特性接口(interface)类型别名(type)
继承支持 extends不支持,需用交叉类型 &
合并同名接口自动合并同名类型别名报错
适用场景描述对象 / 函数的形状联合类型、交叉类型、基础类型别名

举例:接口合并(适合扩展第三方类型)

typescript

运行

// 第一次定义
interface User {
  name: string;
}

// 第二次定义(自动合并)
interface User {
  age: number;
}

// 使用:同时有 name 和 age
const user: User = { name: '孙七', age: 25 };

三、TS 类型进阶

3.1 联合类型(Union Types)

核心概念讲解

联合类型表示 “一个值可以是多种类型中的一种”,语法:类型1 | 类型2 | 类型3

场景解析 + 举例

场景 1:变量的多类型

typescript

运行

// 变量可以是数字或字符串
let id: number | string;
id = 1001; // ✅
id = '1001'; // ✅
id = true; // ❌
场景 2:函数参数的多类型

typescript

运行

// 格式化 ID:数字转字符串,字符串直接返回
function formatId(id: number | string): string {
  // 类型收窄(TS 自动识别 typeof 后的类型)
  if (typeof id === 'number') {
    return id.toString().padStart(4, '0');
  } else {
    return id;
  }
}
formatId(1); // 返回 "0001"
formatId('1'); // 返回 "1"
场景 3:字面量联合类型(限定值范围)

typescript

运行

// 只能是这三个值中的一个
type Direction = 'left' | 'right' | 'top' | 'bottom';
function move(dir: Direction): void {
  console.log(`向${dir}移动`);
}
move('left'); // ✅
move('up'); // ❌ 不在限定范围内

3.2 交叉类型(Intersection Types)

核心概念讲解

交叉类型表示 “合并多个类型的属性”,语法:类型1 & 类型2,最终类型拥有所有类型的属性。

场景解析 + 举例

场景 1:合并对象类型

typescript

运行

type BaseInfo = { name: string; age: number };
type AddressInfo = { city: string; street: string };
// 合并后:有 name、age、city、street
type UserInfo = BaseInfo & AddressInfo;

const user: UserInfo = {
  name: '周八',
  age: 30,
  city: '上海',
  street: '南京路'
};
场景 2:给已有类型扩展属性

typescript

运行

// 原有类型
interface User {
  name: string;
}

// 扩展属性
type UserWithId = User & { id: number };
const u: UserWithId = { name: '吴九', id: 1002 };

3.3 类型收窄(Type Narrowing)

核心概念讲解

类型收窄是指 “在代码块中,TS 自动将联合类型缩小为更具体的类型”,常用手段:typeofinstanceofin、类型守卫。

常用收窄方式 + 举例

方式 1:typeof(判断基本类型)

typescript

运行

function print(val: string | number) {
  if (typeof val === 'string') {
    console.log(val.length); // TS 知道 val 是 string
  } else {
    console.log(val.toFixed(2)); // TS 知道 val 是 number
  }
}
方式 2:instanceof(判断实例类型)

typescript

运行

class Cat {
  meow() { console.log('喵'); }
}
class Dog {
  bark() { console.log('汪'); }
}

function animalCall(animal: Cat | Dog) {
  if (animal instanceof Cat) {
    animal.meow(); // TS 知道是 Cat
  } else {
    animal.bark(); // TS 知道是 Dog
  }
}
方式 3:in(判断对象属性)

typescript

运行

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

function moveAnimal(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim(); // TS 知道是 Fish
  } else {
    animal.fly(); // TS 知道是 Bird
  }
}
方式 4:自定义类型守卫(is 关键字)

typescript

运行

// 定义类型守卫函数
function isNumber(val: unknown): val is number {
  return typeof val === 'number';
}

function handleVal(val: unknown) {
  if (isNumber(val)) {
    console.log(val + 1); // TS 知道 val 是 number
  }
}

3.4 泛型(Generics)- TS 核心重点

核心概念讲解

泛型是 “类型参数”,允许定义函数 / 接口 / 类时不指定具体类型,而是在使用时指定,解决 “类型复用” 问题。

基础语法 + 举例

场景 1:泛型函数(解决多类型复用)

typescript

运行

// 定义泛型函数:T 是类型变量(可自定义名称)
function identity<T>(value: T): T {
  return value;
}

// 使用:指定类型
const str = identity<string>('hello'); // str 是 string 类型
const num = identity<number>(123); // num 是 number 类型

// 简化:TS 自动推导类型
const bool = identity(true); // bool 是 boolean 类型
场景 2:泛型接口

typescript

运行

// 定义泛型接口
interface Result<T> {
  code: number;
  data: T; // data 的类型由泛型决定
}

// 使用:指定 data 为数组类型
const res1: Result<string[]> = {
  code: 200,
  data: ['a', 'b']
};

// 使用:指定 data 为对象类型
const res2: Result<{ name: string }> = {
  code: 200,
  data: { name: '郑十' }
};
场景 3:泛型类

typescript

运行

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

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

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

// 使用:数字栈
const numStack = new Stack<number>();
numStack.push(1);
numStack.pop(); // 返回 number | undefined

// 使用:字符串栈
const strStack = new Stack<string>();
strStack.push('a');
场景 4:泛型约束(限制泛型范围)

typescript

运行

// 定义约束接口
interface Lengthwise {
  length: number;
}

// 泛型 T 必须满足 Lengthwise(有 length 属性)
function logLength<T extends Lengthwise>(val: T): void {
  console.log(val.length);
}

logLength('hello'); // ✅ string 有 length
logLength([1, 2]); // ✅ 数组有 length
logLength(123); // ❌ number 没有 length

3.5 枚举(Enum)

核心概念讲解

枚举是 “命名常量集合”,用于定义一组有名字的常量,提升代码可读性(替代魔法值)。

类型 + 举例

场景 1:数字枚举(默认从 0 开始)

typescript

运行

enum Direction {
  Left, // 0
  Right, // 1
  Top, // 2
  Bottom // 3
}

console.log(Direction.Left); // 0
console.log(Direction[0]); // "Left"(反向映射)

// 自定义初始值
enum Status {
  Success = 200,
  Error = 500
}
console.log(Status.Success); // 200
场景 2:字符串枚举(无反向映射)

typescript

运行

enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE'
}

console.log(Color.Red); // "RED"
// console.log(Color['RED']); // ❌ 字符串枚举无反向映射
场景 3:常量枚举(编译优化)

typescript

运行

// const 枚举:编译时直接替换为值,不生成枚举对象
const enum Week {
  Mon = '周一',
  Tue = '周二'
}

console.log(Week.Mon); // 编译后:console.log("周一");

四、TS 高级特性

4.1 类型断言(Type Assertion)

核心概念讲解

类型断言是 “告诉 TS 编译器,我知道这个值的具体类型”,用于手动指定类型(仅编译期有效,不影响运行时)。

语法 + 场景举例

语法 1:<类型>值(不推荐,JSX 中冲突)
语法 2:值 as 类型(推荐)

typescript

运行

// 场景1:unknown 类型转具体类型
let val: unknown = 'hello';
// 断言为 string
const strLength = (val as string).length;

// 场景2:DOM 元素断言(TS 无法识别 DOM 类型)
const btn = document.getElementById('btn') as HTMLButtonElement;
btn.addEventListener('click', () => {}); // ✅ 知道是按钮元素

// 场景3:双重断言(不推荐,仅特殊场景用)
let num: number = (val as any) as number;

注意点

  • 类型断言不能 “无中生有”,只能断言为兼容的类型(如 let a = 1 as string 会报错);
  • 避免滥用断言,优先用类型守卫。

4.2 索引类型(Index Types)

核心概念讲解

索引类型用于 “动态获取对象的键 / 值类型”,核心语法:keyof(获取键的联合类型)、T[K](获取键对应的值类型)。

举例说明

typescript

运行

interface User {
  name: string;
  age: number;
  city: string;
}

// 1. keyof:获取 User 的键联合类型 → "name" | "age" | "city"
type UserKeys = keyof User;
let key: UserKeys = 'name'; // ✅
key = 'gender'; // ❌

// 2. T[K]:获取键对应的值类型
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { name: '钱十一', age: 35, city: '广州' };
const name = getValue(user, 'name'); // name 是 string 类型
const age = getValue(user, 'age'); // age 是 number 类型

4.3 映射类型(Mapped Types)

核心概念讲解

映射类型用于 “基于已有类型创建新类型”,核心是遍历已有类型的键,修改其属性(如只读、可选)。

常用映射类型 + 举例

场景 1:只读映射(Readonly)

typescript

运行

interface User {
  name: string;
  age: number;
}

// 自定义只读映射类型
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

const user: ReadonlyUser = { name: '孙十二', age: 40 };
user.name = '李十三'; // ❌ 只读属性不可修改
场景 2:可选映射(Partial)

typescript

运行

// 自定义可选映射类型
type PartialUser = {
  [K in keyof User]?: User[K];
};

const user: PartialUser = { name: '周十四' }; // ✅ age 可选
场景 3:TS 内置映射类型

TS 内置了常用映射类型,无需自定义:

  • Partial<T>:所有属性可选;
  • Readonly<T>:所有属性只读;
  • Required<T>:所有属性必选;
  • Pick<T, K>:挑选 T 中的 K 个属性;
  • Omit<T, K>:排除 T 中的 K 个属性。

typescript

运行

// Pick:挑选 name 和 age 属性
type PickUser = Pick<User, 'name' | 'age'>;
const pUser: PickUser = { name: '吴十五', age: 45 };

// Omit:排除 city 属性
type OmitUser = Omit<User, 'city'>;
const oUser: OmitUser = { name: '郑十六', age: 50 };

4.4 条件类型(Conditional Types)

核心概念讲解

条件类型是 “类型层面的三元表达式”,语法:T extends U ? X : Y(如果 T 是 U 的子类型,返回 X,否则返回 Y)。

举例说明

typescript

运行

// 基础条件类型
type IsNumber<T> = T extends number ? true : false;
type A = IsNumber<123>; // A = true
type B = IsNumber<string>; // B = false

// 结合泛型+条件类型
type Filter<T> = T extends string ? T : never;
type StrArray = Filter<string | number | boolean>; // StrArray = string

// TS 内置条件类型:Exclude、Extract、ReturnType
// Exclude:排除类型
type E = Exclude<string | number, string>; // E = number

// Extract:提取类型
type Ex = Extract<string | number, number>; // Ex = number

// ReturnType:获取函数返回值类型
type Fn = () => number;
type R = ReturnType<Fn>; // R = number

4.5 模块与命名空间

核心概念讲解

  • 模块:TS 沿用 ES 模块规范,import/export 导入导出,每个文件是独立模块;
  • 命名空间:用 namespace 定义,用于组织代码(已被模块替代,仅兼容老代码)。

举例说明

场景 1:模块导出 / 导入

typescript

运行

// utils.ts(导出)
export const PI = 3.14;
export function add(a: number, b: number): number {
  return a + b;
}
export type Status = 'success' | 'error';

// index.ts(导入)
import { PI, add, Status } from './utils';
console.log(PI);
add(1, 2);
let s: Status = 'success';
场景 2:命名空间(了解即可)

typescript

运行

namespace MathUtils {
  export const PI = 3.14;
  export function add(a: number, b: number): number {
    return a + b;
  }
}

console.log(MathUtils.PI);
MathUtils.add(1, 2);

五、TS 工程化实践

5.1 TSConfig 详细配置

核心配置解析(按优先级排序)

配置项取值作用
compilerOptions-编译选项核心对象
→ targetES5/ES6/ESNext编译后的 JS 版本
→ moduleCommonJS/ESNext/ES6模块系统
→ outDir字符串(如 ./dist)输出目录
→ rootDir字符串(如 ./src)源文件目录
→ stricttrue/false开启严格模式(必开)
→ strictNullCheckstrue/false严格空检查(strict 包含此配置)
→ noImplicitAnytrue/false禁止隐式 any 类型
→ esModuleInteroptrue/false兼容 CommonJS 模块导入(如 import React from 'react')
→ skipLibChecktrue/false跳过第三方库类型检查(提升编译速度)
include数组(如 ["src/**/*"])要编译的文件
exclude数组(如 ["node_modules"])排除编译的文件

实战配置示例(tsconfig.json)

json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "NodeNext"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

5.2 与构建工具集成(Vite/Webpack)

场景 1:Vite + TS(推荐)

  1. 初始化 Vite 项目:npm create vite@latest ts-vite -- --template react-ts(React+TS)或 vue-ts(Vue+TS);
  2. Vite 内置 TS 支持,无需额外配置,直接编写 .ts/.tsx 文件;
  3. 运行:npm run dev,打包:npm run build

场景 2:Webpack + TS

  1. 安装依赖:npm install ts-loader typescript --save-dev

  2. 配置 webpack.config.js:

    javascript

    运行

    module.exports = {
      entry: './src/index.ts',
      output: {
        filename: 'bundle.js',
        path: __dirname + '/dist'
      },
      module: {
        rules: [
          {
            test: /.ts$/,
            use: 'ts-loader',
            exclude: /node_modules/
          }
        ]
      },
      resolve: {
        extensions: ['.ts', '.js']
      }
    };
    

5.3 类型声明文件(.d.ts)

核心概念讲解

类型声明文件用于 “给 JS 代码添加类型提示”,后缀 .d.ts,分为:

  • 内置声明文件:TS 自带(如 lib.dom.d.ts 包含 DOM 类型);
  • 第三方声明文件:安装 @types/xxx(如 npm install @types/node --save-dev);
  • 自定义声明文件:手写 .d.ts 给自研 JS 库添加类型。

自定义声明文件举例

typescript

运行

// typings.d.ts
// 声明全局变量
declare const PI: number;

// 声明模块(如第三方 JS 库)
declare module 'lodash' {
  export function debounce(fn: Function, delay: number): Function;
}

// 声明接口
declare interface Window {
  myApp: {
    version: string;
  };
}

5.4 ESLint + Prettier 规范 TS 代码

配置步骤

  1. 安装依赖:

    bash

    运行

    npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier --save-dev
    
  2. 新建 .eslintrc.js

    javascript

    运行

    module.exports = {
      parser: '@typescript-eslint/parser',
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module'
      },
      plugins: ['@typescript-eslint'],
      extends: [
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended',
        'prettier'
      ],
      rules: {
        '@typescript-eslint/no-explicit-any': 'warn' // 禁止 any 类型(警告)
      }
    };
    
  3. 新建 .prettierrc

    json

    {
      "semi": true,
      "singleQuote": true,
      "tabWidth": 2,
      "trailingComma": "es5"
    }
    

六、TS 实战项目(第 9-10 周)

6.1 实战项目 1:TS 实现 TodoList(基础)

需求

  • 新增待办、删除待办、标记完成;
  • 全 TS 类型标注,无 any;
  • 本地存储(localStorage)。

核心代码示例

typescript

运行

// 1. 定义类型
interface Todo {
  id: number;
  content: string;
  completed: boolean;
}

// 2. 核心逻辑
class TodoList {
  private todos: Todo[];

  constructor() {
    // 从本地存储加载,类型断言
    this.todos = JSON.parse(localStorage.getItem('todos') || '[]') as Todo[];
  }

  // 新增
  addTodo(content: string): void {
    if (!content) return;
    const todo: Todo = {
      id: Date.now(),
      content,
      completed: false
    };
    this.todos.push(todo);
    this.save();
  }

  // 删除
  deleteTodo(id: number): void {
    this.todos = this.todos.filter(todo => todo.id !== id);
    this.save();
  }

  // 标记完成
  toggleTodo(id: number): void {
    this.todos = this.todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    );
    this.save();
  }

  // 保存到本地存储
  private save(): void {
    localStorage.setItem('todos', JSON.stringify(this.todos));
  }

  // 获取所有待办
  getTodos(): Todo[] {
    return [...this.todos];
  }
}

// 使用
const todoList = new TodoList();
todoList.addTodo('学习 TS');
todoList.toggleTodo(todoList.getTodos()[0].id);
console.log(todoList.getTodos());

6.2 实战项目 2:TS + React/Vue 开发组件(进阶)

以 React 为例:TS 开发 Button 组件

tsx

// 1. 定义类型
import React from 'react';

// 按钮类型
type ButtonType = 'primary' | 'secondary' | 'danger';
// 按钮大小
type ButtonSize = 'small' | 'medium' | 'large';

// 组件属性接口
interface ButtonProps {
  type?: ButtonType;
  size?: ButtonSize;
  disabled?: boolean;
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
}

// 2. 组件实现
const Button: React.FC<ButtonProps> = ({
  type = 'primary',
  size = 'medium',
  disabled = false,
  onClick,
  children
}) => {
  return (
    <button
      className={`btn btn-${type} btn-${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

export default Button;

// 3. 使用组件
const App = () => {
  return (
    <div>
      <Button type="primary" onClick={(e) => console.log(e)}>
        主要按钮
      </Button>
      <Button type="danger" disabled>
        禁用按钮
      </Button>
    </div>
  );
};

七、常见问题与避坑指南

7.1 常见报错及解决

报错信息原因解决方法
Type 'null' is not assignable to type 'string'严格空检查下,null 不能赋值给非空类型1. 开启可选链:val?.length;2. 类型断言:val as string;3. 判空:if (val) {}
Property 'xxx' does not exist on type '{}'对象无此属性定义接口 / 类型别名,标注对象类型
Parameter 'x' implicitly has an 'any' type未标注参数类型,且 noImplicitAny 为 true给参数添加类型标注
Cannot find name 'require'使用 require 但无 Node 类型安装 @types/node

7.2 避坑指南

  1. 不要滥用 anyany 会让 TS 失去意义,优先用 unknown + 类型守卫;
  2. 开启 strict 模式:严格模式能暴露更多类型问题,新手必开;
  3. 优先用接口而非类型别名描述对象:接口支持合并,更适合扩展;
  4. 泛型不要过度封装:简单场景直接标注类型,泛型用于复用场景;
  5. 第三方库无类型:安装 @types/xxx,若无则手动编写 .d.ts 声明。

八、学习资源与工具推荐

8.1 学习资源

  1. 官方文档(中文版):www.typescriptlang.org/zh/docs/(结合本方案看,重点看基础 + 泛型);
  2. 视频教程:B 站「尚硅谷 TS 教程」「李立超 TS 教程」(适合新手);
  3. 实战案例:GitHub 搜索「typescript examples」「ts-todo-list」;
  4. 类型挑战:github.com/type-challe…(进阶练习)。

8.2 工具推荐

  1. 编辑器:VS Code(必装);
  2. 类型查询:tsplay.dev/(在线 TS 编辑器,可调试类型);
  3. 类型生成:transform.tools/json-to-ts(JSON 转 TS 接口);
  4. 调试工具:VS Code 内置 TS 调试(F5 启动)。

学习计划建议

阶段时间核心任务
基础阶段1-2 周掌握基本类型、变量、函数、接口、类型别名
进阶阶段3-4 周掌握联合 / 交叉类型、类型收窄、泛型、枚举
高级阶段5-6 周掌握类型断言、索引 / 映射 / 条件类型、模块
工程化阶段7-8 周掌握 TSConfig、构建工具集成、类型声明
实战阶段9-10 周完成 TodoList 和框架组件开发,刷类型挑战

关键原则

  1. 边学边练:每学一个知识点,立即写代码验证(如学完泛型,写一个泛型函数);
  2. 从简单项目入手:先写纯 TS 小工具,再结合框架开发;
  3. 多看源码:查看 React/Vue 源码中的 TS 写法,学习大厂规范;
  4. 遇到问题先查类型:报错时先看 TS 提示,理解类型不匹配的原因。