TypeScript在线调试 (opens new window)
# 1.什么是TypeScript
Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。
Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。
# 2.数据类型
# 一、基础类型(Primitive Types)
# 1. 原始类型
类型 | 描述 | 示例 |
---|---|---|
string | 文本类型 | let name: string = "Alice" |
number | 数字(含整数/浮点数) | let age: number = 25 |
boolean | 布尔值 | let isDone: boolean = true |
null | 空值 | let n: null = null |
undefined | 未定义 | let u: undefined = undefined |
symbol | 唯一标识符(ES6) | const sym: symbol = Symbol('key') |
bigint | 大整数(ES2020) | let big: bigint = 100n |
# 2. 特殊原始类型
类型 | 描述 | 示例 |
---|---|---|
void | 表示无返回值(函数) | function log(): void { console.log('Done') } |
never | 表示永不返回(如抛出错误) | function error(): never { throw new Error() } |
# 二、对象类型(Object Types)
# 1. 数组
语法 | 示例 |
---|---|
类型[] | let nums: number[] = [1, 2] |
Array<类型> | let strs: Array<string> = ['a', 'b'] |
只读数组 | const roArr: readonly number[] = [1, 2] |
# 2. 元组(Tuple)
固定长度和类型的数组:
let tuple: [string, number] = ["Alice", 25];
tuple[0] = "Bob"; // OK
tuple[1] = "30"; // 报错:第二个元素必须是 number
2
3
# 3. 对象
// 接口定义
interface User {
name: string;
age?: number; // 可选属性
readonly id: number; // 只读属性
}
// 使用
const user: User = { name: "Alice", id: 1 };
user.name = "Bob"; // OK
user.id = 2; // 报错:id 是只读的
2
3
4
5
6
7
8
9
10
11
# 4. 函数
// 函数类型
type AddFunc = (a: number, b: number) => number;
// 实现
const add: AddFunc = (x, y) => x + y;
2
3
4
# 三、高级类型(Advanced Types)
# 1. 联合类型(Union)
let value: string | number;
value = "hello"; // OK
value = 42; // OK
value = true; // 报错
2
3
4
# 2. 交叉类型(Intersection)
interface A { a: number }
interface B { b: string }
type C = A & B; // { a: number, b: string }
const obj: C = { a: 1, b: "text" };
2
3
4
5
# 3. 字面量类型(Literal)
let direction: "up" | "down" | "left" | "right";
direction = "up"; // OK
direction = "north"; // 报错
2
3
# 4. 枚举(Enum)
enum Color {
Red = "RED",
Green = "GREEN",
}
let color: Color = Color.Red;
2
3
4
5
# 5. 类型别名(Type Alias)
type Point = {
x: number;
y: number;
};
const p: Point = { x: 10, y: 20 };
2
3
4
5
6
# 6. 索引签名(Index Signature)
interface StringDict {
[key: string]: string; // 任意 string 键,值必须是 string
}
const dict: StringDict = {
name: "Alice",
age: "25" // 报错:值必须是 string
};
2
3
4
5
6
7
8
# 四、其他类型
# 1. any 与 unknown
类型 | 描述 |
---|---|
any | 关闭类型检查(慎用) |
unknown | 安全版 any ,操作前需类型检查 |
let unsafe: any = "hello";
unsafe.toFixed(); // 编译通过,运行时可能报错
let safe: unknown = "hello";
if (typeof safe === "string") {
safe.toUpperCase(); // 安全操作
}
2
3
4
5
6
# 2. 泛型(Generics)
function identity<T>(arg: T): T {
return arg;
}
const output = identity<string>("text"); // 显式指定类型
const inferred = identity(42); // 自动推断为 number
2
3
4
5
# 3.类型声明和类型推断的区别
类型声明是显式地为变量或函数指定类型,而类型推断是TypeScript根据赋值语句右侧的值自动推断变量的类型。例如:
// 类型声明
let x: number;
x = 10;
// 类型推断
let y = 20; // TypeScript会自动推断y的类型为number
2
3
4
5
# 4.接口的作用以及使用场景,和类型别名的区别
# 一、接口(Interface)的作用
接口主要用于定义对象的形状(Shape),即描述对象应该有哪些属性和方法。它的核心作用是:
- 约定数据结构:强制对象必须符合某种结构。
- 实现继承(extends):通过继承扩展其他接口。
- 类实现(implements):类可以通过 implements 关键字实现接口的约束。
- 定义对象类型
interface User {
name: string;
age: number;
}
const user: User = { name: "Alice", age: 25 }; // 必须符合接口结构
2
3
4
5
- 函数类型
interface SearchFunc {
(source: string, keyword: string): boolean;
}
const search: SearchFunc = (src, kw) => src.includes(kw);
2
3
4
- 可索引类型(如数组、字典)
interface StringArray {
[index: number]: string;
}
const arr: StringArray = ["a", "b"];
2
3
4
- 类的实现约束
interface Animal {
sound(): void;
}
class Dog implements Animal {
sound() { console.log("Woof!"); }
}
2
3
4
5
6
# 二、类型别名(Type Alias)的作用
类型别名通过 type 关键字为任意类型(包括原始类型、联合类型、交叉类型等)命名,作用类似于“快捷方式”。
- 联合类型(Union)
type ID = string | number;
const id1: ID = "123";
const id2: ID = 123;
2
3
- 交叉类型(Intersection)
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
const person: Person = { name: "Alice", age: 25 };
2
3
4
- 复杂类型映射
type PartialUser = Partial<User>; // 所有属性变为可选
- 元组类型
type Point = [number, number];
const p: Point = [10, 20];
2
- 类型推导与工具类型
type UserKeys = keyof User; // "name" | "age"
# 5.泛型(Generics)
# 一. 什么是泛型?
泛型类似于 “类型占位符”,它允许我们在定义函数、类或接口时 不预先指定具体类型,而是在使用时动态传入类型。这样可以:
- 提高代码复用性:同一套逻辑可以适用于多种类型。
- 增强类型安全:避免 any 带来的类型丢失问题。
- 提供更好的类型推断:TS 能自动推断泛型类型。
假设我们要写一个函数,返回传入的参数:
function identity(arg: any): any {
return arg;
}
2
3
使用 any 虽然能接受任何类型,但 丢失了类型信息(返回值类型无法确定)。 泛型可以解决这个问题:
function identity<T>(arg: T): T {
return arg; // 返回值的类型与输入类型一致
}
const result = identity<string>("Hello"); // result 的类型是 string
2
3
4
# 二、如何创建泛型函数?
基本类型参数
泛型函数在函数名后用 <T>
声明类型参数,T 可以是任意字母(如 K, V 等)。
function echo<T>(value: T): T {
return value;
}
const str = echo<string>("hello"); // str: string
const num = echo<number>(42); // num: number
// TS 可以自动推断类型,通常可以省略显式声明:
const inferredStr = echo("hello"); // 自动推断为 string
const inferredNum = echo(42); // 自动推断为 number
2
3
4
5
6
7
8
9
多类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair<string, number>("Alice", 25); // [string, number]
2
3
4
泛型约束(Generic Constraints)
如果需要对泛型类型进行限制,可以用 extends:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // OK,string 有 length
logLength([1, 2, 3]); // OK,数组有 length
logLength(42); // 报错:number 没有 length
2
3
4
5
6
7
8
9
# 三、泛型的实际用途
# (1) 通用数据结构和算法
// 数组操作(如 map, filter)
function mapArray<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
const numbers = [1, 2, 3];
const strings = mapArray(numbers, (n) => n.toString()); // string[]
2
3
4
5
6
# (2) 动态指定 API 返回的数据类型
interface ApiResponse<T> {
data: T;
status: number;
}
function fetchUser(): ApiResponse<{ name: string }> {
return { data: { name: "Alice" }, status: 200 };
}
2
3
4
5
6
7
# (3) React 组件 Props
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <div>{items.map(renderItem)}</div>;
}
// 使用
<List<number>
items={[1, 2, 3]}
renderItem={(n) => <div>{n}</div>}
/>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6.枚举(Enum)
# 一、枚举是什么?
枚举允许开发者定义一组相关的常量,并为它们赋予有意义的名称。例如:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
2
3
4
5
6
# 二、枚举的应用案例
# (1) 状态管理
enum OrderStatus {
Pending = "PENDING",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED",
}
function updateOrder(status: OrderStatus) {
console.log(`Order status: ${status}`);
}
updateOrder(OrderStatus.Shipped); // "Order status: SHIPPED"
updateOrder("PENDING"); // 报错:必须使用枚举
2
3
4
5
6
7
8
9
10
11
# (2) HTTP 状态码
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
}
function handleResponse(code: HttpStatus) {
if (code === HttpStatus.OK) {
console.log("Success!");
}
}
2
3
4
5
6
7
8
9
10
11
# 三、枚举的类型
TypeScript 支持 数字枚举、字符串枚举 和 异构枚举(混合 string 和 number)。
# (1) 数字枚举(默认)
enum StatusCode {
Success, // 0(默认从 0 开始)
NotFound = 404, // 可以手动赋值
ServerError, // 405(自动递增)
}
console.log(StatusCode.Success); // 0
console.log(StatusCode[404]); // "NotFound"(反向映射)
2
3
4
5
6
7
# (2) 字符串枚举
enum LogLevel {
Error = "ERROR",
Warn = "WARN",
Info = "INFO",
}
console.log(LogLevel.Error); // "ERROR"
// LogLevel["ERROR"] 不能反向查找(字符串枚举不支持)
2
3
4
5
6
7
# (3) 异构枚举(混合 string 和 number)
enum MixedEnum {
Yes = 1,
No = "NO",
}
console.log(MixedEnum.Yes); // 1
console.log(MixedEnum.No); // "NO"
2
3
4
5
6
# 四、常量枚举(const enum)
常量枚举是一种优化手段,它会在编译时 内联枚举值,减少运行时开销。
const enum LogLevel {
Error = "ERROR",
Warn = "WARN",
}
// 编译后:
console.log("ERROR" /* LogLevel.Error */); // 直接替换为 "ERROR"
2
3
4
5
6
# 7.处理可空类型(Nullable Types)
在TypeScript中,可空类型是指一个变量可以存储特定类型的值,也可以存储null或undefined。
为了声明一个可空类型,可以使用联合类型(Union Types)
let numberOrNull: number | null = 10;
numberOrNull = null; // 可以赋值为null
let stringOrUndefined: string | undefined = "Hello";
stringOrUndefined = undefined; // 可以赋值为undefined
2
3
4
5
# 8.什么是联合类型和交叉类型
联合类型表示一个值可以是多种类型中的一种
而交叉类型表示一个新类型,它包含了多个类型的特性。
# 联合类型示例:
// typescript
let myVar: string | number;
myVar = "Hello"; // 合法
myVar = 123; // 合法
2
3
4
5
# 交叉类型示例:
interface A {
a(): void;
}
interface B {
b(): void;
}
type C = A & B; // 表示同时具备 A 和 B 的特性
2
3
4
5
6
7
# 9.命名空间(Namespace)和模块(Module)
# 一. 命名空间(Namespace)
命名空间是 TypeScript 特有的 代码分组机制,用于在全局作用域内划分逻辑单元,避免命名冲突。
特点
- 作用域隔离:内部变量/类/函数默认仅在命名空间内可见。
- 全局扩展:可通过 declare global 或合并声明扩展全局类型。
- 依赖手动加载:需通过
<script>
标签或打包工具合并文件。
使用场景
- 旧版代码迁移(兼容全局变量模式)。
- 为第三方库提供类型声明(如 jQuery 的 $.ajax)。
- 小型项目快速组织代码(不推荐大型项目)。
// 定义命名空间
namespace MyMath {
export function add(a: number, b: number): number {
return a + b;
}
}
// 使用
console.log(MyMath.add(1, 2)); // 3
2
3
4
5
6
7
8
# 多文件命名空间
通过 /// <reference path="..." />
手动合并文件:
// file1.ts
namespace MyMath {
export const PI = 3.14;
}
// file2.ts
/// <reference path="file1.ts" />
namespace MyMath {
export function circleArea(radius: number): number {
return PI * radius * radius; // 访问其他文件的成员
}
}
2
3
4
5
6
7
8
9
10
11
# 二. 模块(Module)
模块是 ES6 标准 的代码封装方式,每个文件是一个独立模块,通过 import/export 管理依赖。
- 特点
- 文件即模块:每个 .ts 文件默认是模块,顶层作用域变量不污染全局。
- 显式依赖:必须通过 import 引入其他模块。
- 现代项目标配:与 Webpack/Rollup 等打包工具深度集成。
- 使用场景
- 大型项目或前端框架(React/Vue)。
- 需要 Tree Shaking 优化代码体积。
- 团队协作时代码解耦。
// math.ts(模块文件)
export function add(a: number, b: number): number {
return a + b;
}
// app.ts(使用模块)
import { add } from './math';
console.log(add(1, 2)); // 3
2
3
4
5
6
7
# 模块类型
ES Modules(推荐)
import { func } from './module';
export default class MyClass {}
2
CommonJS(Node.js):
const fs = require('fs');
module.exports = { myFunc };
2
# 10.类型断言(Type Assertion)
类型断言(Type Assertion)是 TypeScript 中一种 显式告诉编译器变量类型 的机制,它允许开发者覆盖 TypeScript 的默认类型推断
类型断言类似于其他语言中的 类型转换,但 不进行运行时检查,仅影响编译阶段的类型推断。
- 它有两种语法形式:
- 尖括号语法(
<Type>value
) - as 语法(value as Type,推荐在 JSX 中使用)
- 尖括号语法(
// 场景:从 API 获取的数据,开发者知道是 `User` 类型,但 TS 推断为 `any`
const userData: any = { name: "Alice", age: 25 };
// 类型断言明确告诉编译器这是 `User` 类型
interface User {
name: string;
age: number;
}
// 语法1:尖括号(不适用于 JSX)
const user1 = <User>userData;
// 语法2:as(推荐,兼容 JSX)
const user2 = userData as User;
console.log(user1.name); // "Alice"(类型安全访问)
2
3
4
5
6
7
8
9
10
11
12
# 类型断言 vs 类型声明
特性 | 类型断言(Type Assertion) | 类型声明(Type Annotation) |
---|---|---|
作用时机 | 覆盖编译器的类型推断 | 声明变量时指定类型 |
运行时影响 | 无(纯编译时行为) | 无 |
适用场景 | 开发者比编译器更了解类型时 | 变量初始化时明确类型 |
// 类型声明(初始化时明确类型)
let age: number = 25;
// 类型断言(覆盖推断)
let unknownData: unknown = "Hello";
let strLength = (unknownData as string).length;
2
3
4
5
# 使用场景
# (1) 处理 any 或 unknown 类型
const data: unknown = JSON.parse('{"name": "Bob"}');
const userName = (data as { name: string }).name; // 安全访问
2
# (2) 访问联合类型的特定成员
function formatInput(input: string | number) {
// 明确告诉编译器此时是 string
if ((input as string).trim) {
return (input as string).trim();
}
return input.toString();
}
2
3
4
5
6
7
# (3) 强制转换 DOM 元素类型
const button = document.getElementById("submit") as HTMLButtonElement;
button.disabled = true; // 明确知道是按钮元素
2
# (4) 非空断言(! 后缀)
// 告诉编译器变量一定不是 null/undefined
function greet(name?: string) {
console.log(name!.toUpperCase()); // 谨慎使用!
}
2
3
4
# 注意事项
- 避免滥用
类型断言会绕过 TypeScript 的类型检查,滥用可能导致运行时错误。
// 错误示范:
const num = "123" as number; // 编译通过,但运行时是字符串!
console.log(num.toFixed(2)); // 运行时报错
2
3
# 11.TypeScript中的可选参数和默认参数
- 可选参数允许函数中的某些参数不传值,在参数后面加上问号?表示可选。
function greet(name: string, greeting?: string) {
if (greeting) {
return `${greeting}, ${name}!`;
} else {
return `Hello, ${name}!`;
}
}
2
3
4
5
6
7
- 默认参数允许在声明函数时为参数指定默认值,这样如果调用时未提供参数值,则会使用默认值。
function greet(name: string, greeting: string = "Hello") {
return `${greeting}, ${name}!`;
}
2
3
# 12.索引类型(Index Types)
索引类型(Index Types)是 TypeScript 中一种 动态访问和操作对象属性类型 的高级特性,它通过 键名映射 和 类型运算 实现灵活的类型安全操作。
索引类型允许开发者通过 字符串或数字字面量类型 动态访问对象属性的类型,核心依赖两个操作符:
- keyof T:获取类型 T 的所有可访问键名的联合类型。
T[K]
:通过键名 K 获取类型 T 中对应属性的类型。
interface User {
name: string;
age: number;
address: string;
}
// 获取 User 所有键名的联合类型
type UserKeys = keyof User; // "name" | "age" | "address"
// 动态获取属性类型
type NameType = User["name"]; // string
type AgeType = User["age"]; // number
2
3
4
5
6
7
8
9
10
# 核心优势
# (1) 类型安全的动态属性访问
避免硬编码属性名,通过泛型约束确保代码类型安全。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 返回值类型动态推断为 T[K]
}
const user: User = { name: "Alice", age: 25, address: "123 Main St" };
const name = getProperty(user, "name"); // string(类型正确)
const age = getProperty(user, "age"); // number(类型正确)
const invalid = getProperty(user, "email"); // 报错:"email" 不是 User 的键
2
3
4
5
6
7
8
# (2) 映射类型(Mapped Types)的基础
索引类型是生成映射类型(如 Partial<T>
、Readonly<T>
)的核心。
// 内置的 Partial<T> 实现原理
type Partial<T> = {
[P in keyof T]?: T[P]; // 将所有属性变为可选
};
type PartialUser = Partial<User>;
// 等价于 { name?: string; age?: number; address?: string; }
2
3
4
5
6
7
# (3) 约束对象键名的合法性
在泛型中限制函数参数必须为对象的有效键名。
function setProperty<T, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): void {
obj[key] = value; // 键和值类型严格匹配
}
setProperty(user, "age", 30); // 正确
setProperty(user, "age", "30"); // 报错:类型不匹配
setProperty(user, "email", "test"); // 报错:无效键名
2
3
4
5
6
7
8
9
10
# 实际应用场景
# (1) 类型安全的字典对象
type Dictionary<T> = {
[key: string]: T; // 任意字符串键,值类型为 T
};
const users: Dictionary<User> = {
alice: { name: "Alice", age: 25 },
bob: { name: "Bob", age: 30 }
};
2
3
4
5
6
7
8
# 13.const 和 readonly 对比
const
和 readonly
都是用于增强代码不可变性的机制,但它们在作用范围、适用场景和约束级别上有显著区别
特性 | const | readonly |
---|---|---|
作用目标 | 变量绑定(Variable Binding) | 属性(Property)或索引签名 |
约束级别 | 禁止重新赋值 | 禁止属性修改 |
适用场景 | 基础类型、对象引用 | 对象属性、数组、类成员、接口 |
可变性绕过 | 不能绕过(严格约束) | 可通过类型断言/拷贝绕过 |
运行时保护 | 无(纯编译时) | 无(纯编译时),需配合 Object.freeze |
典型用途 | 声明常量 | 定义不可变的对象结构 |
# 具体场景对比
# (1) 基础类型
// const:变量不可重新赋值
const PI = 3.14;
PI = 3.14159; // 报错:Cannot assign to 'PI'
// readonly:不适用于基础类型(仅用于属性)
let readonlyNum: readonly number = 1; // 无意义,通常不这样用
2
3
4
5
# (2) 对象
// const:对象引用不可变,但属性可修改
const user = { name: "Alice" };
user.name = "Bob"; // 允许修改属性
user = { name: "Charlie" }; // 报错:不能重新赋值
// readonly:属性不可修改
interface ReadonlyUser {
readonly name: string;
}
const user: ReadonlyUser = { name: "Alice" };
user.name = "Bob"; // 报错:Cannot assign to 'name'
2
3
4
5
6
7
8
9
10
# (3) 数组
// const:数组引用不可变,元素可修改
const arr = [1, 2, 3];
arr[0] = 10; // 允许修改元素
arr = [4, 5, 6]; // 报错:不能重新赋值
// readonly:元素不可修改
const readonlyArr: readonly number[] = [1, 2, 3];
readonlyArr[0] = 10; // 报错:Index signature in type 'readonly number[]' only permits reading
2
3
4
5
6
7
8
# (4) 类与接口
class Person {
readonly id: number; // 实例化后不可修改
constructor(id: number) {
this.id = id;
}
}
const person = new Person(1);
person.id = 2; // 报错:Cannot assign to 'id'
2
3
4
5
6
7
8
# 如何选择?
- 优先用 const 的场景
- 声明基础类型的常量(如 const MAX_SIZE = 100)。
- 确保对象/数组的引用不变(防止重新赋值)。
- 优先用 readonly 的场景
- 定义接口或类的不可变属性(如 readonly id: string)。
- 限制函数参数不被修改(如 function log(config: Readonly
<Config>
))。 - 配合 Readonly
<T>
工具类型快速生成不可变对象。
# 总结
- const:保护变量引用不被重新赋值,适用于所有数据类型。
- readonly:保护对象属性不被修改,适用于接口、类和数组。
- 联合使用:const + Readonly
<T>
可实现完全不可变结构。
# 14.any 类型的作用与滥用后果
# 一、any 的核心作用
# (1) 动态类型兼容
允许变量接受任意类型的值,相当于关闭类型检查:
let dynamicData: any = "Hello";
dynamicData = 42; // 允许从 string 切换到 number
dynamicData = { id: 1 }; // 允许赋值为对象
2
3
# (2) 渐进式迁移 JavaScript 代码
在将 JS 项目迁移到 TS 时,any 可作为临时过渡手段
// 旧 JS 代码迁移示例
function legacyParse(data) { // 隐式 any
return JSON.parse(data);
}
// 初步迁移为 TS(暂时用 any)
function tsParse(data: any): any {
return JSON.parse(data);
}
2
3
4
5
6
7
8
# (3) 与无类型库交互
处理第三方 JS 库或动态 API 响应时
const thirdPartyLib: any = require('untyped-lib');
const result = thirdPartyLib.method(); // 不触发类型报错
2
# 二、滥用 any 的后果
# (1) 类型安全彻底失效
function calculateTotal(price: any, quantity: any): any {
return price * quantity; // 如果传入字符串,返回 NaN(无编译时报错)
}
2
3
- 后果:运行时错误(如 undefined is not a function)无法在编译时捕获。
# (2) 阻碍类型系统优化
let unsafe: any = "Hello";
const len = unsafe.length; // 不报错,但 unsafe 可能是 number
const arr = unsafe.split(","); // 编译通过,运行时可能崩溃
2
3
# 三. 替代方案(避免滥用 any)
# (1) 使用更精确的顶层类型
场景 | 替代方案 | 优点 |
---|---|---|
未知对象结构 | unknown | 必须类型检查后才能操作 |
部分属性可选 | Partial<T> | 明确哪些属性可能缺失 |
动态键值对象 | Record<string, T> | 约束值的类型 |
// 用 unknown 替代 any(安全操作需类型收窄)
function safeParse(data: string): unknown {
return JSON.parse(data);
}
const result = safeParse('{"id":1}');
if (result && typeof result === 'object' && 'id' in result) {
console.log(result.id); // 类型安全访问
}
2
3
4
5
6
7
8
# (2) 类型断言(as)替代部分 any
当开发者比编译器更了解类型时:
const element = document.getElementById("root") as HTMLElement; // 非 any
# (3) 泛型保持灵活性
function identity<T>(arg: T): T {
return arg; // 不丢失类型信息
}
2
3
# (4) 逐步替换遗留 any
// @ts-expect-error - TODO: 迁移后替换为具体类型
const legacyData: any = fetchData();
2
# 四、何时可以合理使用 any?
- (1) 临时快速原型开发
- 短期验证逻辑时使用,完成后立即替换为具体类型。
- (2) 类型体操过于复杂时
- 当类型系统无法简洁表达复杂逻辑时(如递归类型深度过大)。
- (3) 类型声明文件(.d.ts)中
- 为无类型 JS 库编写声明时,暂时用 any 占位:
declare module "untyped-module" {
export const config: any; // 后续逐步补充具体类型
}
2
3
# 15.TS 中 this 的注意事项
# 一、默认情况下 this 的类型问题
在非严格模式下,未指定类型的 this 会被推断为 any,失去类型安全:
// 隐式 any
function logThis() {
console.log(this.name); // this 是 any,无类型检查
}
2
3
4
# 解决方案:启用 --noImplicitThis
在 tsconfig.json 中启用该选项,强制显式声明 this 类型:
{
"compilerOptions": {
"noImplicitThis": true
}
}
2
3
4
5
# 二、函数中的 this 类型
# (1) 普通函数
需手动指定 this 类型(通过第一个参数):
interface User {
name: string;
}
function greet(this: User) {
console.log(`Hello, ${this.name}`);
}
const user = { name: "Alice" };
greet.call(user); // 正确
greet(); // 报错:this 未绑定
2
3
4
5
6
7
8
9
# (2) 箭头函数
箭头函数继承外层 this,无法通过参数声明类型,需通过变量类型推断:
class Timer {
seconds = 0;
start() {
setInterval(() => {
console.log(this.seconds++); // this 正确指向 Timer 实例
}, 1000);
}
}
2
3
4
5
6
7
8
# 三、类中的 this
# (1) 类方法中的 this
默认指向当前类实例,类型自动推断
class Counter {
count = 0;
increment() {
this.count++; // this: Counter
}
}
2
3
4
5
6
# (2) 回调函数中的 this 丢失
将类方法作为回调传递时,this 可能丢失
class Button {
text = "Click me";
handleClick() {
console.log(this.text); // 运行时可能报错
}
}
const button = new Button();
document.addEventListener("click", button.handleClick); // this 指向 DOM 元素
2
3
4
5
6
7
8
修复方法
- 使用箭头函数
class Button {
handleClick = () => {
console.log(this.text); // this 永远指向实例
};
}
2
3
4
5
- 显式绑定 this
document.addEventListener("click", button.handleClick.bind(button));
# 16.协变、逆变、双变和抗变
协变(Covariance)、逆变(Contravariance)、双变(Bivariance) 和 抗变(Invariance) 描述了复杂类型(如函数、泛型)的子类型关系如何传递
# 一. 基本概念
# (1) 子类型(Subtyping)
若类型 A 是类型 B 的子类型(记作 A <: B),则 A 的值可以安全赋值给 B 的变量。 例如:Cat <: Animal(猫是动物的子类型)。
# (2) 变型(Variance)
描述容器类型(如 Array<T>
、函数参数)的子类型关系如何随内部类型 T 变化。
# 二. 四种变型
# (1) 协变(Covariance)
- 定义:如果 A <: B,则 Container
<A>
<: Container<B>
。 - 特点:子类型关系与内部类型方向一致。
- 示例:TypeScript 的数组和返回值类型是协变的:
interface Animal { name: string }
interface Cat extends Animal { meow(): void }
let animals: Animal[] = [];
let cats: Cat[] = [{ name: "Whiskers", meow: () => {} }];
animals = cats; // 协变:Cat[] <: Animal[]
2
3
4
5
6
# (2) 逆变(Contravariance)
- 定义:如果 A <: B,则 Container
<B>
<: Container<A>
。 - 特点:子类型关系与内部类型方向相反。
- 示例:TypeScript 的函数参数类型是逆变的(默认情况下):
type AnimalHandler = (animal: Animal) => void;
type CatHandler = (cat: Cat) => void;
let handleAnimal: AnimalHandler = (a: Animal) => {};
let handleCat: CatHandler = (c: Cat) => { c.meow() };
// 逆变:AnimalHandler <: CatHandler
handleCat = handleAnimal; // 允许
handleAnimal = handleCat; // 报错(除非启用 strictFunctionTypes: false)
2
3
4
5
6
7
8
9
# (3) 双变(Bivariance)
- 定义:Container
<A>
和 Container<B>
互相兼容,无论 A 和 B 的关系。 - 特点:最宽松但最不安全,TypeScript 中函数参数的默认行为(非严格模式)。
- 示例:
// 非严格模式下(tsconfig.json 中 strictFunctionTypes: false)
handleAnimal = handleCat; // 允许
handleCat = handleAnimal; // 允许
2
3
# (4) 抗变(Invariance)
- 定义:Container
<A>
和 Container<B>
仅当 A === B 时才兼容。 - 特点:最严格,TypeScript 中泛型默认行为(如 interface Box
<T>
{ value: T })。 - 示例:
interface Box<T> { value: T }
let animalBox: Box<Animal> = { value: { name: "Milo" } };
let catBox: Box<Cat> = { value: { name: "Whiskers", meow: () => {} } };
animalBox = catBox; // 报错(抗变)
catBox = animalBox; // 报错
2
3
4
5
6