一、学习前准备(认知 + 环境)
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 环境搭建(动手落地)
核心步骤讲解
-
安装 Node.js(v14+):TS 编译依赖 Node 环境,官网 nodejs.org/ 下载;
-
全局安装 TS:
npm install -g typescript,验证:tsc -v(显示版本则成功); -
配置编辑器:推荐 VS Code(内置 TS 支持),安装插件:
- TypeScript Hero:增强 TS 语法提示;
- ESLint:规范 TS 代码;
-
初始化第一个 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(强制类型检查) |
实操示例
-
在 src 目录新建
index.ts:typescript
运行
let name: string = 'TS 学习'; console.log(name); -
编译 TS:
tsc(自动读取 tsconfig.json,生成 dist/index.js); -
运行 JS:
node dist/index.js(输出 “TS 学习”)。
二、TS 核心基础
2.1 基本类型(TS 最核心的 “类型标注”)
核心概念讲解
TS 为 JS 原始类型增加了类型标注,核心基本类型包括:number、string、boolean、null、undefined、symbol、bigint,以及特殊类型 void、any、never。
逐类型解析 + 举例
| 类型 | 说明 | 正确示例 | 错误示例 |
|---|---|---|---|
| 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 自动将联合类型缩小为更具体的类型”,常用手段:typeof、instanceof、in、类型守卫。
常用收窄方式 + 举例
方式 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 | - | 编译选项核心对象 |
| → target | ES5/ES6/ESNext | 编译后的 JS 版本 |
| → module | CommonJS/ESNext/ES6 | 模块系统 |
| → outDir | 字符串(如 ./dist) | 输出目录 |
| → rootDir | 字符串(如 ./src) | 源文件目录 |
| → strict | true/false | 开启严格模式(必开) |
| → strictNullChecks | true/false | 严格空检查(strict 包含此配置) |
| → noImplicitAny | true/false | 禁止隐式 any 类型 |
| → esModuleInterop | true/false | 兼容 CommonJS 模块导入(如 import React from 'react') |
| → skipLibCheck | true/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(推荐)
- 初始化 Vite 项目:
npm create vite@latest ts-vite -- --template react-ts(React+TS)或vue-ts(Vue+TS); - Vite 内置 TS 支持,无需额外配置,直接编写
.ts/.tsx文件; - 运行:
npm run dev,打包:npm run build。
场景 2:Webpack + TS
-
安装依赖:
npm install ts-loader typescript --save-dev; -
配置 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 代码
配置步骤
-
安装依赖:
bash
运行
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier --save-dev -
新建
.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 类型(警告) } }; -
新建
.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 避坑指南
- 不要滥用
any:any会让 TS 失去意义,优先用unknown+ 类型守卫; - 开启
strict模式:严格模式能暴露更多类型问题,新手必开; - 优先用接口而非类型别名描述对象:接口支持合并,更适合扩展;
- 泛型不要过度封装:简单场景直接标注类型,泛型用于复用场景;
- 第三方库无类型:安装
@types/xxx,若无则手动编写.d.ts声明。
八、学习资源与工具推荐
8.1 学习资源
- 官方文档(中文版):www.typescriptlang.org/zh/docs/(结合本方案看,重点看基础 + 泛型);
- 视频教程:B 站「尚硅谷 TS 教程」「李立超 TS 教程」(适合新手);
- 实战案例:GitHub 搜索「typescript examples」「ts-todo-list」;
- 类型挑战:github.com/type-challe…(进阶练习)。
8.2 工具推荐
- 编辑器:VS Code(必装);
- 类型查询:tsplay.dev/(在线 TS 编辑器,可调试类型);
- 类型生成:transform.tools/json-to-ts(JSON 转 TS 接口);
- 调试工具:VS Code 内置 TS 调试(F5 启动)。
学习计划建议
| 阶段 | 时间 | 核心任务 |
|---|---|---|
| 基础阶段 | 1-2 周 | 掌握基本类型、变量、函数、接口、类型别名 |
| 进阶阶段 | 3-4 周 | 掌握联合 / 交叉类型、类型收窄、泛型、枚举 |
| 高级阶段 | 5-6 周 | 掌握类型断言、索引 / 映射 / 条件类型、模块 |
| 工程化阶段 | 7-8 周 | 掌握 TSConfig、构建工具集成、类型声明 |
| 实战阶段 | 9-10 周 | 完成 TodoList 和框架组件开发,刷类型挑战 |
关键原则
- 边学边练:每学一个知识点,立即写代码验证(如学完泛型,写一个泛型函数);
- 从简单项目入手:先写纯 TS 小工具,再结合框架开发;
- 多看源码:查看 React/Vue 源码中的 TS 写法,学习大厂规范;
- 遇到问题先查类型:报错时先看 TS 提示,理解类型不匹配的原因。