Appearance
TypeScript 深度解析与最佳实践指南
TypeScript 核心概念
什么是 TypeScript?
TypeScript 是由微软开发的开源编程语言,具有以下核心特性:
- JavaScript 超集:完全兼容 JavaScript,支持所有 ES5/ES6+ 语法
- 静态类型系统:编译时类型检查,提前发现潜在错误
- 面向对象特性:支持类、接口、泛型等高级特性
- 工具链支持:提供强大的代码补全、重构和导航功能
- 大型项目支持:被 VSCode、Angular、Vue3、React 等大型项目采用
安装与编译
bash
# 全局安装 TypeScript
npm install -g typescript
# 编译单个文件
tsc hello.ts
# 初始化配置文件
tsc --init
配置文件深度解析 (tsconfig.json)
json
{
"compilerOptions": {
/* 基本配置 */
"target": "ES2022", // 编译目标版本: ES3, ES5, ES2015-ES2022, ESNext
"module": "ESNext", // 模块系统: CommonJS, AMD, System, UMD, ES6
"lib": ["ES2022", "DOM"], // 包含的库文件
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源文件目录
/* 类型检查 */
"strict": true, // 启用所有严格类型检查
"noImplicitAny": true, // 禁止隐式 any 类型
"strictNullChecks": true, // 严格的 null 检查
/* 模块解析 */
"moduleResolution": "node", // 模块解析策略
"baseUrl": "./", // 非绝对路径模块解析基准
"paths": { // 路径映射
"@/*": ["src/*"]
},
/* 高级特性 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true, // 生成装饰器元数据
"esModuleInterop": true // 改进 CommonJS/ES 模块互操作
/* 其他重要配置 */
"declaration": true, // 生成 .d.ts 类型声明文件
"sourceMap": true, // 生成 sourcemap
"removeComments": false // 保留注释
},
"include": ["src/**/*.ts"], // 包含文件
"exclude": ["node_modules"] // 排除文件
}
基础类型与变量
布尔类型 (boolean)
ts
let isActive: boolean = false;
let hasPermission: boolean = true;
数字类型 (number)
ts
let decimal: number = 6; // 十进制
let hex: number = 0xf00d; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制
字符串类型 (string)
ts
let firstName: string = "John";
let lastName: string = 'Doe';
let fullName: string = `${firstName} ${lastName}`; // 模板字符串
数组类型 (array)
ts
// 两种声明方式
let numbers: number[] = [1, 2, 3]; // 元素类型后接[]
let strings: Array<string> = ["a", "b"]; // 使用泛型语法
元组类型 (tuple)
ts
// 定义固定类型和长度的数组
let userInfo: [string, number] = ["Alice", 30];
// 访问元素有正确的类型推断
console.log(userInfo[0].substring(1)); // "lice" - string方法
console.log(userInfo[1].toFixed(2)); // "30.00" - number方法
任意类型 (any)
ts
// 可以赋值为任何类型
let dynamicValue: any = 10;
dynamicValue = "Hello";
dynamicValue = false;
// 实际应用场景:处理第三方库或动态内容
let root: HTMLElement | null = document.getElementById('root');
// 使用非空断言操作符 (!) 告诉编译器值不为 null
root!.style.color = 'red';
root!.addEventListener('click', () => {
console.log('Element clicked');
});
TypeScript 特殊类型深度解析
null 和 undefined 类型
ts
// 非严格模式 (strictNullChecks: false)
let num: number;
num = 1; // ✅ 允许
num = null; // ✅ 允许
num = undefined; // ✅ 允许
// 严格模式 (strictNullChecks: true)
let num: number;
num = 1; // ✅ 允许
num = null; // ❌ 错误:不能将类型"null"分配给类型"number"
num = undefined; // ❌ 错误:不能将类型"undefined"分配给类型"number"
// 显式联合类型声明
let safeNum: number | null | undefined;
safeNum = 1; // ✅ 允许
safeNum = null; // ✅ 允许
safeNum = undefined; // ✅ 允许
最佳实践:
- 始终启用
strictNullChecks
提高代码安全性 - 使用联合类型明确处理可能的空值
- 使用可选链操作符
?.
安全访问嵌套属性
ts
interface User {
address?: {
street?: string
}
}
const user: User = {};
const street = user.address?.street; // 安全访问,避免运行时错误
void 类型
ts
// 函数无返回值
function logMessage(message: string): void {
console.log(message);
// 严格模式下只能返回 undefined
// return undefined; // ✅ 允许
// return null; // ❌ 错误 (strictNullChecks: true)
}
// void 变量声明
let unusable: void = undefined; // ✅ 允许
unusable = null; // ❌ 错误 (strictNullChecks: true)
// 实际应用:事件处理
button.addEventListener('click', (): void => {
console.log('Button clicked');
});
never 类型
ts
// 抛出异常的函数
function throwError(message: string): never {
throw new Error(message);
}
// 永不返回的函数
function infiniteLoop(): never {
while (true) {
// 无限循环
}
}
// 类型收窄的 never
type AllStrings = string | number;
function handleValue(val: AllStrings) {
if (typeof val === "string") {
// 处理字符串
} else if (typeof val === "number") {
// 处理数字
} else {
// 此分支 val 为 never 类型
const exhaustiveCheck: never = val;
}
}
never 与 void 的区别
特性 | void | never |
---|---|---|
表示含义 | 没有返回值 | 永不出现的值 |
可赋值内容 | 仅 undefined (严格模式) | 无任何值可赋值 |
函数返回 | 正常返回但无值 | 函数无法正常完成执行 |
变量声明 | 可声明为 undefined | 不能声明变量 |
类型收窄 | 不参与类型收窄 | 用于穷尽检查 |
使用场景 | 无返回值的函数 | 抛出异常/无限循环的函数 |
Symbol 类型
ts
// 创建唯一标识
const userId = Symbol('userID');
const sessionId = Symbol('sessionID');
// 作为对象属性
const user = {
[userId]: '12345',
name: 'John Doe',
[sessionId]: 'abcde'
};
console.log(user[userId]); // '12345'
// 全局 Symbol 注册表
const globalSymbol = Symbol.for('app.global');
const sameGlobalSymbol = Symbol.for('app.global');
console.log(globalSymbol === sameGlobalSymbol); // true
console.log(Symbol.keyFor(globalSymbol)); // 'app.global'
// 内置 Symbols - 实现可迭代对象
const customCollection = {
items: [10, 20, 30],
[Symbol.iterator]: function* () {
for (const item of this.items) {
yield item;
}
}
};
for (const num of customCollection) {
console.log(num); // 10, 20, 30
}
Symbol 最佳实践:
- 创建唯一属性键避免命名冲突
- 使用
Symbol.for()
在全局注册表中共享 Symbols - 实现内置 Symbols 如
Symbol.iterator
创建自定义迭代器 - 使用
Object.getOwnPropertySymbols()
获取对象的所有 Symbol 属性
枚举类型
普通枚举
ts
// 基本枚举(数字枚举)
enum Direction {
Up, // 0 (默认从0开始)
Down, // 1
Left, // 2
Right // 3
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up" (反向映射)
// 指定初始值
enum StatusCodes {
Success = 200, // 200
NotFound = 404, // 404
Error = 500 // 500
}
console.log(StatusCodes.Success); // 200
// 混合枚举
enum MixedEnum {
// 字符串成员
Admin = "ADMIN",
User = "USER",
// 数字成员 (可以自动递增)
Read = 1, // 1
Write, // 2
Execute // 3
}
console.log(MixedEnum.Admin); // "ADMIN"
console.log(MixedEnum.Write); // 2
// 计算成员示例
enum FileAccess {
// 常量成员
None,
Read = 1 << 1, // 2 (位运算)
Write = 1 << 2, // 4
ReadWrite = Read | Write, // 6 (组合权限)
// 字符串计算成员 (TypeScript 5.0+)
Description = "Level: " + ReadWrite.toString() // "Level: 6"
}
console.log(FileAccess.ReadWrite); // 6
console.log(FileAccess.Description); // "Level: 6"
常数枚举 (const enums)
ts
// 常数枚举 - 编译时会被内联
const enum LogLevel {
Error = 1,
Warn,
Info,
Debug
}
// 使用示例 - 编译后会被替换为字面量
let currentLevel = LogLevel.Error;
function log(message: string, level: LogLevel) {
if (level === LogLevel.Error) {
console.error(message);
} else if (level === LogLevel.Warn) {
console.warn(message);
}
// ...
}
log("Critical issue!", LogLevel.Error);
// 编译后的JavaScript代码:
// let currentLevel = 1;
// function log(message, level) {
// if (level === 1) { ... }
// }
// log("Critical issue!", 1);
枚举最佳实践
优先使用常量枚举:
ts// 减少运行时开销 const enum UserRole { Admin = "ADMIN", Editor = "EDITOR", Viewer = "VIEWER" }
避免数字枚举的陷阱:
ts// 不良实践 - 易出错的数字枚举 enum BadStatus { Active = 1, Pending, // 2 - 如果插入新值会破坏顺序 Completed // 3 } // 良好实践 - 显式赋值 enum GoodStatus { Active = 10, Pending = 20, Completed = 30 }
使用字符串枚举提高可读性:
ts// 更易调试和维护 enum MediaType { JPEG = "image/jpeg", PNG = "image/png", PDF = "application/pdf" } function handleFile(type: MediaType) { // 直接使用可读的字符串值 }
枚举编译行为对比
特性 | 普通枚举 | 常量枚举 |
---|---|---|
运行时存在 | 是 (生成真实对象) | 否 (编译时内联) |
支持计算成员 | 是 | 否 (仅允许常量表达式) |
反向映射 | 是 (数字成员) | 否 |
适用场景 | 需要运行时访问的枚举 | 仅编译时使用的枚举 |
输出大小 | 增加代码体积 | 无额外代码体积 |
性能影响 | 有运行时开销 | 无运行时开销 |
现代替代方案 (TypeScript 5.0+)
ts
// 使用 as const 实现枚举类似功能
const LogLevel = {
Error: 0,
Warn: 1,
Info: 2,
Debug: 3
} as const;
type LogLevel = typeof LogLevel[keyof typeof LogLevel];
// 使用示例
function log(message: string, level: LogLevel) {
// ...
}
log("Info message", LogLevel.Info);
何时使用枚举 vs 对象常量:
- 需要反向映射 → 选择枚举
- 需要数字自动递增 → 选择枚举
- 需要最小化运行时开销 → 选择常量枚举或对象常量
- 需要严格的类型安全 → 选择对象常量 +
as const
枚举是 TypeScript 强大的特性之一,合理使用可以:
- 提高代码可读性和可维护性
- 创建有意义的命名常量集合
- 简化复杂状态管理
- 通过类型检查减少错误
类型自动推断
ts
// 未初始化时推断为 any 类型
let uninitialized;
uninitialized = 10; // ✅ 允许
uninitialized = []; // ✅ 允许
// 初始化时推断具体类型
let initialized = 10; // 推断为 number 类型
initialized = '1'; // ❌ 错误:不能将类型 "string" 分配给类型 "number"
联合类型
ts
// 定义联合类型
let value: string | number;
// 非严格模式 (strictNullChecks: false)
console.log(value.toString()); // ✅ 访问共有方法 (string 和 number 都有 toString)
value = 3;
console.log(value.toFixed(2)); // ✅ 访问 number 方法
value = 'zhufeng';
console.log(value.length); // ✅ 访问 string 属性
// 严格模式 (strictNullChecks: true)
console.log(value.toString()); // ❌ 错误:变量 'value' 在赋值前被使用
联合类型与交叉类型对比
基本用法
ts
interface A {
name: string;
}
interface B {
age: number;
}
// 联合类型:只需满足 A 或 B 之一
type UnionAB = A | B;
const union1: UnionAB = { name: "Alice" }; // ✅ 满足 A
const union2: UnionAB = { age: 30 }; // ✅ 满足 B
// 交叉类型:必须同时满足 A 和 B
type IntersectionAB = A & B;
const intersection: IntersectionAB = {
name: "Alice",
age: 30
}; // ✅ 同时满足 A 和 B
// 不可能的类型
type Impossible = string & number; // 推断为 never
对象类型的交叉类型
ts
type TypeA = {
a: string;
b: {
name: string;
}
}
type TypeB = {
a: number; // 与 TypeA 的 a 冲突
b: {
age: number;
}
}
type TypeC = TypeA & TypeB;
// 冲突属性成为 never
type PropA = TypeC['a']; // never
// 嵌套属性合并
type PropB = TypeC['b']; // { name: string; age: number; }
// 实际使用
const validObj: TypeC['b'] = {
name: "Alice",
age: 30
};
类型断言技巧
基础断言
ts
let value: string | number;
// 非严格模式
console.log((value as string).length); // ✅
console.log((value as number).toFixed(2)); // ✅
// 严格模式 (未赋值时报错)
console.log((value as string).length); // ❌ 错误:变量 'value' 在赋值前被使用
value = "test";
console.log((value as string).length); // ✅ 4
双重断言
ts
interface UserInfo {
name: string;
age: number;
}
// 处理 API 响应时的双重断言
fetch('https://api.example.com/user')
.then(response => response.json())
.then(data => {
// 先断言为 unknown,再断言为目标类型
const user = data as unknown as UserInfo;
console.log(user.name, user.age);
});
// 处理第三方库返回
const unsafeData: any = getExternalData();
const safeData = unsafeData as unknown as UserInfo;
字面量类型与类型字面量
字面量类型
ts
// 特定值作为类型
const directionUp: 'Up' = 'Up';
const directionDown: 'Down' = 'Down';
// 联合字面量类型
type Direction = 'Up' | 'Down' | 'Left' | 'Right';
function move(direction: Direction) {
console.log(`Moving ${direction}`);
}
move('Up'); // ✅
move('North'); // ❌ 错误:参数类型不匹配
// 实际应用:Redux action types
type ActionType = 'ADD_TODO' | 'REMOVE_TODO' | 'TOGGLE_TODO';
function dispatch(action: ActionType) {
// ...
}
类型字面量
ts
// 内联类型定义
type Point = {
x: number;
y: number;
};
const p: Point = { x: 10, y: 20 };
// 函数类型字面量
type Greeter = (name: string) => string;
const greet: Greeter = name => `Hello, ${name}!`;
// 复杂类型组合
type UserProfile = {
id: number;
name: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
};
最佳实践总结
优先使用类型推断:
ts// 良好:利用类型推断 const items = [1, 2, 3]; // 推断为 number[] // 避免冗余类型声明 const items: number[] = [1, 2, 3];
合理使用联合类型:
ts// 处理多种可能类型 function formatInput(input: string | number) { return typeof input === 'string' ? input.trim() : input.toFixed(2); }
谨慎使用类型断言:
ts// 优先使用类型守卫 if (typeof value === 'string') { console.log(value.length); } // 必要时使用双重断言 const safeValue = unsafeValue as unknown as TargetType;
利用字面量类型增强类型安全:
ts// 精确的状态管理 type LoadingState = 'idle' | 'loading' | 'succeeded' | 'failed'; let currentState: LoadingState = 'idle';
使用类型别名简化复杂类型:
ts// 简化复杂类型 type ApiResponse<T> = { data: T; status: number; timestamp: Date; }; type UserResponse = ApiResponse<{ id: number; name: string }>;
函数参数类型
ts
function hello(name:string):void {
console.log('hello',name);
}
hello('hl');
函数类型约束
ts
type typeAdd = (x:number,y:number)=>number;
let add:typeAdd = (x,y)=>{
return x + y;
}
add(1,2)
函数参数处理
可选参数
ts
function printUser(name: string, age?: number): void {
console.log(`Name: ${name}, Age: ${age || 'unknown'}`);
}
printUser("Alice"); // Name: Alice, Age: unknown
printUser("Bob", 30); // Name: Bob, Age: 30
默认参数
ts
function request(url: string, method: string = "GET") {
console.log(`Sending ${method} request to ${url}`);
}
request("/api/users"); // Sending GET request to /api/users
request("/api/posts", "POST"); // Sending POST request to /api/posts
剩余参数
ts
function calculateAverage(...scores: number[]): number {
const total = scores.reduce((sum, score) => sum + score, 0);
return total / scores.length;
}
console.log(calculateAverage(85, 90, 78)); // 84.333...
函数重载
ts
// 重载签名
function formatInput(input: string): string;
function formatInput(input: number): string;
// 实现签名
function formatInput(input: any): string {
if (typeof input === "string") {
return input.trim().toUpperCase();
} else if (typeof input === "number") {
return input.toFixed(2);
}
return "";
}
console.log(formatInput(" hello ")); // "HELLO"
console.log(formatInput(3.1415926)); // "3.14"
类特性详解
参数属性
ts
// 传统方式
class User {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 使用参数属性(简写)
class UserShort {
constructor(public name: string, public age: number) {}
}
const user1 = new User("Alice", 30);
const user2 = new UserShort("Bob", 25);
访问修饰符
ts
class Account {
// 公共属性(默认)
public id: number;
// 受保护属性(仅限类和子类访问)
protected balance: number;
// 私有属性(仅限类内部访问)
private pin: string;
// 只读属性(初始化后不可修改)
readonly createdAt: Date;
constructor(id: number, pin: string) {
this.id = id;
this.balance = 0;
this.pin = pin;
this.createdAt = new Date();
}
// 公共方法
public deposit(amount: number): void {
this.balance += amount;
}
// 受保护方法
protected verifyPin(pin: string): boolean {
return this.pin === pin;
}
}
class SavingsAccount extends Account {
withdraw(amount: number, pin: string): boolean {
if (this.verifyPin(pin) {
this.balance -= amount;
return true;
}
return false;
}
}
const account = new SavingsAccount(123, "1234");
account.deposit(1000);
console.log(account.withdraw(500, "1234")); // true
静态成员
ts
class MathUtils {
// 静态属性
static PI = 3.1415926535;
// 静态方法
static circleArea(radius: number): number {
return this.PI * radius ** 2;
}
}
console.log(MathUtils.PI); // 3.1415926535
console.log(MathUtils.circleArea(5)); // 78.5398163375
继承与抽象类
ts
// 抽象类
abstract class Animal {
constructor(public name: string) {}
// 抽象方法(必须由子类实现)
abstract makeSound(): void;
// 普通方法
move(distance: number = 0): void {
console.log(`${this.name} moved ${distance}m`);
}
}
// 具体子类
class Dog extends Animal {
constructor(name: string) {
super(name);
}
makeSound(): void {
console.log("Woof! Woof!");
}
// 扩展方法
fetch(item: string): void {
console.log(`${this.name} fetched the ${item}`);
}
}
const dog = new Dog("Buddy");
dog.makeSound(); // "Woof! Woof!"
dog.move(10); // "Buddy moved 10m"
dog.fetch("ball");// "Buddy fetched the ball"
接口系统
类实现接口
ts
interface Loggable {
log(message: string): void;
}
interface Serializable {
serialize(): string;
}
// 实现多个接口
class Logger implements Loggable, Serializable {
constructor(private name: string) {}
log(message: string): void {
console.log(`[${this.name}] ${message}`);
}
serialize(): string {
return JSON.stringify({ name: this.name });
}
}
const logger = new Logger("App");
logger.log("Starting application"); // [App] Starting application
console.log(logger.serialize()); // {"name":"App"}
接口继承
ts
interface Shape {
color: string;
}
interface Circle extends Shape {
radius: number;
getArea(): number;
}
const myCircle: Circle = {
color: "red",
radius: 10,
getArea() {
return Math.PI * this.radius ** 2;
}
};
console.log(myCircle.getArea()); // 314.159...
函数类型接口
ts
interface Transformer {
(input: string): string;
}
const toUpper: Transformer = (str) => str.toUpperCase();
const reverse: Transformer = (str) => str.split("").reverse().join("");
console.log(toUpper("hello")); // "HELLO"
console.log(reverse("world")); // "dlrow"
可索引接口
ts
// 数组约束
interface StringArray {
[index: number]: string;
}
const fruits: StringArray = ["Apple", "Banana", "Cherry"];
console.log(fruits[1]); // "Banana"
// 对象约束
interface NumberDictionary {
[key: string]: number;
length: number; // 必须匹配索引签名
}
const scores: NumberDictionary = {
math: 90,
science: 85,
length: 2
};
装饰器应用
类装饰器
ts
// 装饰器工厂
function LogClass(prefix: string) {
// 类装饰器
return function (constructor: Function) {
console.log(`${prefix}: ${constructor.name} class initialized`);
};
}
@LogClass("[LOGGER]")
class DatabaseService {
constructor() {
console.log("Database service created");
}
}
// 控制台输出:
// [LOGGER]: DatabaseService class initialized
// Database service created
抽象类 vs 接口对比
特性 | 抽象类 | 接口 |
---|---|---|
实例化 | ❌ 不能实例化 | ❌ 不能实例化 |
方法实现 | ✅ 可包含具体实现 | ❌ 仅声明不实现 |
属性初始化 | ✅ 可初始化属性 | ❌ 仅声明属性 |
访问修饰符 | ✅ 支持 public/protected/private | ❌ 所有成员隐式 public |
继承 | 单继承(extends) | 多实现(implements) |
构造函数 | ✅ 可以有构造函数 | ❌ 不能有构造函数 |
使用场景 | 提供基础实现,要求子类完善 | 定义契约,要求类遵循规范 |
最佳实践总结
函数设计:
- 使用可选参数处理可选值
- 默认参数简化调用
- 剩余参数处理可变参数
- 函数重载提供类型友好的多态接口
类设计原则:
ts// 良好实践:使用参数属性简化代码 class Point { constructor(public x: number, public y: number) {} } // 访问控制:合理使用 public/protected/private class SecureData { private secret: string; constructor(secret: string) { this.secret = secret; } public getMaskedSecret(): string { return this.secret.replace(/./g, '*'); } }
接口应用:
ts// 定义服务契约 interface PaymentProcessor { process(amount: number): boolean; refund(transactionId: string): boolean; } // 实现支付服务 class StripeProcessor implements PaymentProcessor { process(amount: number) { /* ... */ } refund(id: string) { /* ... */ } }
抽象类使用场景:
ts// 提供基础实现 abstract class BaseAPI { constructor(protected baseUrl: string) {} abstract fetchData(endpoint: string): Promise<any>; protected buildUrl(endpoint: string): string { return `${this.baseUrl}/${endpoint}`; } } class UsersAPI extends BaseAPI { async fetchData(endpoint: string) { const url = this.buildUrl(endpoint); return fetch(url).then(res => res.json()); } }
泛型
泛型基础概念
泛型(Generics)允许在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定类型,提供代码复用性和类型安全性。
ts
// 泛型函数
function createArray<T>(length: number, value: T): T[] {
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(value);
}
return result;
}
// 使用示例
const stringArray = createArray<string>(3, "x"); // string[]
const numberArray = createArray(3, 1); // number[] (类型推断)
泛型类
ts
class Collection<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getFirst(): T | undefined {
return this.items[0];
}
// 泛型约束方法
filterByType<U extends T>(type: new (...args: any[]) => U): U[] {
return this.items.filter((item): item is U => item instanceof type);
}
}
// 使用示例
const numberCollection = new Collection<number>();
numberCollection.add(1);
numberCollection.add(2);
console.log(numberCollection.getFirst()); // 1
class User { name = "" }
const userCollection = new Collection<User>();
userCollection.add(new User());
const filtered = userCollection.filterByType(User); // User[]
泛型接口
ts
// 函数类型接口
interface Comparator<T> {
(a: T, b: T): number;
}
// 对象类型接口
interface Repository<T> {
getById(id: string): T | undefined;
save(entity: T): void;
}
// 使用示例
const numberComparator: Comparator<number> = (a, b) => a - b;
console.log([10, 5, 8].sort(numberComparator)); // [5, 8, 10]
class UserRepo implements Repository<User> {
private users: User[] = [];
getById(id: string) {
return this.users.find(u => u.id === id);
}
save(user: User) {
this.users.push(user);
}
}
高级泛型技巧
多类型参数
ts
function merge<A, B>(a: A, b: B): A & B {
return { ...a, ...b };
}
const user = { name: "Alice" };
const account = { balance: 100 };
const merged = merge(user, account); // { name: string; balance: number }
默认泛型类型
ts
function createApiClient<T = object>(baseUrl: string): {
get: (path: string) => Promise<T>;
post: (path: string, data: any) => Promise<T>;
} {
return {
get: async (path) => (await fetch(`${baseUrl}${path}`)).json(),
post: async (path, data) => (await fetch(`${baseUrl}${path}`, {
method: 'POST',
body: JSON.stringify(data)
})).json()
};
}
// 使用默认类型
const api = createApiClient("/api");
const data = await api.get("/users"); // object
// 指定类型
const userApi = createApiClient<User>("/api/users");
const user = await userApi.get("/123"); // User
泛型约束
ts
interface Identifiable {
id: string;
}
// 约束泛型必须实现Identifiable接口
function logId<T extends Identifiable>(entity: T): void {
console.log(entity.id);
}
// 约束泛型为构造函数
function createInstance<T>(ctor: new () => T): T {
return new ctor();
}
泛型类型别名
ts
// 响应数据格式
type ApiResponse<T> = {
data: T;
status: number;
timestamp: Date;
}
// 分页数据
type Paginated<T> = {
items: T[];
total: number;
page: number;
perPage: number;
}
// 使用示例
const userResponse: ApiResponse<User> = {
data: { id: "1", name: "Alice" },
status: 200,
timestamp: new Date()
};
const paginatedUsers: Paginated<User> = {
items: [{ id: "1", name: "Alice" }],
total: 1,
page: 1,
perPage: 10
};
泛型接口 vs 泛型类型别名
特性 | 泛型接口 | 泛型类型别名 |
---|---|---|
创建新类型名 | ✅ 创建新类型名 | ❌ 只是别名 |
扩展 (extends) | ✅ 支持 | ❌ 不支持 |
实现 (implements) | ✅ 类可实现接口 | ❌ 类不能实现类型别名 |
联合类型 | ❌ 不适合联合类型 | ✅ 适合联合/交叉类型 |
声明合并 | ✅ 支持多声明合并 | ❌ 不支持 |
错误信息显示 | ✅ 显示接口名 | ❌ 显示原始类型结构 |
递归类型 | ❌ 有限支持 | ✅ 完整支持 |
最佳实践指南
优先使用接口定义对象结构:
ts// 良好实践:使用接口 interface Response<T> { data: T; status: number; } // 避免:类型别名定义对象 type Response<T> = { data: T; status: number; }
使用类型别名处理复杂类型:
ts// 联合类型 type Result<T> = Success<T> | Error; // 元组类型 type Point3D = [number, number, number];
合理使用泛型约束:
ts// 约束键名存在 function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { name: "Alice", age: 30 }; getProperty(user, "name"); // ✅ getProperty(user, "email"); // ❌ 错误
避免过度泛型化:
ts// 不良实践:过度泛型化 function parse<T>(input: string): T { return JSON.parse(input); } // 良好实践:使用类型断言 function parse<T>(input: string): T { return JSON.parse(input) as T; }
泛型在React中的应用
tsx
// 泛型组件
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function GenericList<T>({ items, renderItem }: ListProps<T>) {
return <div>{items.map(renderItem)}</div>;
}
// 使用示例
<GenericList
items={[{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]}
renderItem={user => <div key={user.id}>{user.name}</div>}
/>
// 自定义hooks中的泛型
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
// 使用示例
const [user, setUser] = useLocalStorage<User>("user", { name: "" });
TypeScript 类型兼容性深度解析
接口兼容性规则
ts
interface Animal {
name: string;
age: number;
}
interface Person {
name: string;
age: number;
gender: number;
}
// 函数参数兼容性检查
function getName(animal: Animal): string {
return animal.name;
}
const p: Person = {
name: 'zhufeng',
age: 10,
gender: 0
};
getName(p); // ✅ 允许:Person 满足 Animal 结构
// 直接赋值类型检查
const a: Animal = {
name: 'zhufeng',
age: 10,
gender: 0 // ❌ 错误:Animal 类型不存在 gender 属性
};
基本类型兼容性
ts
// 联合类型兼容性
let num: string | number;
const str: string = 'zhufeng';
num = str; // ✅ 允许:string 是 string | number 的子类型
// 结构兼容性
let num2: { toString(): string };
const str2: string = 'jiagou';
num2 = str2; // ✅ 允许:string 类型具有 toString 方法
类兼容性(结构类型系统)
ts
class Animal {
name: string = '';
}
class Bird extends Animal {
swing: number = 2;
}
// 子类实例可赋值给父类变量
let a: Animal;
a = new Bird(); // ✅ 允许:Bird 包含 Animal 的所有属性
// 父类实例不可赋值给子类变量(缺少子类特有属性)
let b: Bird;
b = new Animal(); // ❌ 错误:Animal 缺少 swing 属性
// 无继承关系但结构相同的类可相互赋值
class Car {
name: string = '';
}
const animal: Animal = new Car(); // ✅ 允许:结构相同
函数兼容性规则
参数兼容性
ts
type SumFunc = (a: number, b: number) => number;
let sum: SumFunc;
// 参数个数兼容性
const f1 = (a: number, b: number) => a + b; // ✅
const f2 = (a: number) => a; // ✅
const f3 = () => 0; // ✅
const f4 = (a: number, b: number, c: number) => a + b + c; // ❌
sum = f1;
sum = f2;
sum = f3;
sum = f4; // 错误:参数个数超过目标类型
返回值兼容性
ts
type GetPerson = () => { name: string; age: number };
let getPerson: GetPerson;
// 返回值类型兼容性
const g1 = () => ({ name: 'zhufeng', age: 10 }); // ✅
const g2 = () => ({ name: 'zhufeng', age: 10, gender: '男' }); // ✅
const g3 = () => ({ name: 'zhufeng' }); // ❌
getPerson = g1;
getPerson = g2;
getPerson = g3; // 错误:缺少 age 属性
泛型兼容性规则
ts
// 1. 空接口(未使用泛型参数)相互兼容
interface Empty<T> {}
let x: Empty<string>;
let y: Empty<number>;
x = y; // ✅ 允许
// 2. 使用泛型参数的接口不兼容
interface NotEmpty<T> {
data: T;
}
let x1: NotEmpty<string>;
let y1: NotEmpty<number>;
x1 = y1; // ❌ 错误:类型参数不同
// 等价于以下非泛型接口:
interface NotEmptyString { data: string }
interface NotEmptyNumber { data: number }
let xx2!: NotEmptyString;
let yy2!: NotEmptyNumber;
xx2 = yy2; // ❌ 错误:类型不兼容
枚举兼容性规则
ts
enum Colors { Red, Yellow }
enum Directions { Up, Down }
let c: Colors;
c = Colors.Red; // ✅
c = 1; // ✅ 数字兼容枚举
c = '1'; // ❌ 字符串不兼容
let n: number;
n = 1; // ✅
n = Colors.Red; // ✅ 枚举兼容数字
// 不同枚举类型不兼容
let d: Directions;
d = Colors.Red; // ❌ 错误:Colors 与 Directions 不兼容
类型兼容性总结表
类型 | 兼容规则 | 示例 |
---|---|---|
接口 | 源类型必须包含目标类型的所有属性 | Person → Animal ✅ |
基本类型 | 子类型可赋值给父类型 | string → `string |
类 | 结构相同即兼容(名称无关) | Car → Animal ✅ |
函数参数 | 目标参数个数 ≤ 源参数个数 | (a) => ... → (a,b) => ... ✅ |
函数返回值 | 目标返回值类型必须包含源返回值类型 | {name,age} → {name,age,gender} ✅ |
泛型(空接口) | 未使用泛型参数的接口相互兼容 | Empty<string> → Empty<number> ✅ |
泛型(非空接口) | 类型参数相同时才兼容 | NotEmpty<string> → NotEmpty<number> ❌ |
枚举 | 枚举与数字相互兼容,不同枚举互不兼容 | Colors.Red → number ✅ |
TypeScript 类型保护深度解析
类型保护核心概念
类型保护是 TypeScript 在编译时通过特定表达式确保作用域内变量类型的机制,主要通过以下方式实现:
typeof 类型保护
ts
function processValue(value: string | number | boolean) {
if (typeof value === 'string') {
// 此作用域内 value 被识别为 string
console.log(value.toUpperCase());
} else if (typeof value === 'number') {
// 此作用域内 value 被识别为 number
console.log(value.toFixed(2));
} else {
// 此作用域内 value 被识别为 boolean
console.log(!value);
}
}
instanceof 类型保护
ts
class Animal {
name!: string;
}
class Bird extends Animal {
swing!: number;
}
function describeAnimal(animal: Animal) {
if (animal instanceof Bird) {
// 此作用域内 animal 被识别为 Bird
console.log(`Bird with ${animal.swing} wings`);
} else {
// 此作用域内 animal 被识别为 Animal
console.log(`Animal named ${animal.name}`);
}
}
null/undefined 保护
ts
function safeAccess(value: string | null | undefined) {
// 显式 null/undefined 检查
if (value == null) {
return '';
}
// 此作用域内 value 被识别为 string
return value.trim();
}
function logValue(value: string | null) {
// 链判断运算符
console.log(value?.toUpperCase());
// 非空断言 (谨慎使用)
console.log(value!.length);
}
链判断运算符(?.)
ts
interface UserProfile {
address?: {
street?: {
name: string;
};
};
}
const getStreetName = (user: UserProfile): string => {
return user.address?.street?.name || "Unknown Street";
};
const callIfExists = (fn?: () => void) => {
fn?.(); // 安全调用函数
};
可辨识联合类型
ts
// 通过共同字段区分类型
type NetworkResponse =
| { status: 'loading'; progress: number }
| { status: 'success'; data: string }
| { status: 'error'; code: number };
function handleResponse(response: NetworkResponse) {
switch (response.status) {
case 'loading':
console.log(`Progress: ${response.progress}%`);
break;
case 'success':
console.log(`Data: ${response.data}`);
break;
case 'error':
console.error(`Error code: ${response.code}`);
break;
}
}
in 操作符类型保护
ts
interface Bird {
fly(): void;
swing: number;
}
interface Fish {
swim(): void;
fin: number;
}
function move(animal: Bird | Fish) {
if ('swing' in animal) {
// 此作用域内 animal 被识别为 Bird
animal.fly();
} else {
// 此作用域内 animal 被识别为 Fish
animal.swim();
}
}
自定义类型保护(类型谓词)
ts
// 类型谓词函数
function isBird(animal: Bird | Fish): animal is Bird {
return 'fly' in animal && typeof animal.fly === 'function';
}
function handleAnimal(animal: Bird | Fish) {
if (isBird(animal)) {
// 此作用域内 animal 被识别为 Bird
animal.fly();
} else {
// 此作用域内 animal 被识别为 Fish
animal.swim();
}
}
类型保护综合应用
ts
type PaymentMethod =
| { type: 'credit'; cardNumber: string; expiry: string }
| { type: 'paypal'; email: string }
| { type: 'crypto'; walletAddress: string };
function processPayment(method: PaymentMethod) {
if (method.type === 'credit') {
console.log(`Processing credit card: ${method.cardNumber}`);
} else if (method.type === 'paypal') {
console.log(`Processing PayPal: ${method.email}`);
} else {
console.log(`Processing crypto: ${method.walletAddress}`);
}
}
// 复杂场景:嵌套类型保护
interface Order {
id: string;
payment?: PaymentMethod;
customer?: {
name: string;
contact?: {
email: string;
phone?: string;
};
};
}
function sendConfirmation(order: Order) {
// 链判断 + 类型保护
const email = order.customer?.contact?.email;
if (email) {
console.log(`Sending confirmation to ${email}`);
} else if (order.payment?.type === 'credit') {
console.log('Sending SMS confirmation');
} else {
console.log('Cannot send confirmation');
}
}
类型保护机制对比表
类型保护方式 | 适用场景 | 优点 | 注意事项 |
---|---|---|---|
typeof | 基本类型区分 | 简单直观,性能好 | 不能区分自定义类型 |
instanceof | 类实例区分 | 面向对象场景最佳实践 | 不能用于接口 |
in 操作符 | 对象属性检测 | 适用于无继承关系的类型 | 可能受原型链影响 |
可辨识联合类型 | 状态机、Redux action等 | 类型安全,模式匹配清晰 | 需要设计共有字段 |
自定义类型保护 | 复杂类型判断 | 灵活,可封装复杂逻辑 | 需要手动维护类型谓词 |
链判断运算符 ?. | 深层嵌套属性访问 | 简洁,避免冗长的条件判断 | 返回undefined可能掩盖问题 |
非空断言 ! | 开发者明确知道值不为空时 | 简化代码 | 可能引发运行时错误,谨慎使用 |
最佳实践指南
优先使用内置类型保护:
ts// 良好:使用 typeof 处理基本类型 if (typeof value === 'string') { /* ... */ } // 良好:使用 instanceof 处理类实例 if (error instanceof SyntaxError) { /* ... */ }
设计可辨识联合类型:
ts// 状态管理最佳实践 type AsyncState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error };
谨慎使用非空断言:
ts// 仅在确保非空时使用 function saveUser(user: User) { const id = user.id!; // 确保user.id存在 // ... }
封装复杂类型判断:
ts// 自定义类型保护复用复杂判断 function isAdmin(user: User): user is AdminUser { return user.roles.includes('admin') && 'adminPermissions' in user; }
结合链判断与类型保护:
tsfunction getContactEmail(user?: User): string | null { return user?.contacts?.find(c => c.type === 'email')?.value || null; }
企业级应用场景
Redux 状态管理
ts
type UserAction =
| { type: 'USER_LOADING' }
| { type: 'USER_LOADED'; payload: User }
| { type: 'USER_UPDATE'; payload: Partial<User> }
| { type: 'USER_ERROR'; error: string };
function userReducer(state: UserState, action: UserAction): UserState {
switch (action.type) {
case 'USER_LOADING':
return { ...state, loading: true };
case 'USER_LOADED':
return { ...state, data: action.payload, loading: false };
case 'USER_UPDATE':
return { ...state, data: { ...state.data, ...action.payload } };
case 'USER_ERROR':
return { ...state, error: action.error, loading: false };
default:
return state;
}
}
API 响应处理
ts
type ApiResponse<T> =
| { status: 'success'; data: T; timestamp: Date }
| { status: 'error'; code: number; message: string }
| { status: 'loading' };
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
return { status: 'error', code: response.status, message: response.statusText };
}
const data = await response.json() as T;
return { status: 'success', data, timestamp: new Date() };
} catch (error) {
return { status: 'error', code: 500, message: (error as Error).message };
}
}
表单验证
ts
type ValidationResult =
| { valid: true; value: string }
| { valid: false; error: string };
function validateEmail(email: string): ValidationResult {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email) {
return { valid: false, error: 'Email is required' };
}
if (!regex.test(email)) {
return { valid: false, error: 'Invalid email format' };
}
return { valid: true, value: email };
}
// 使用
const result = validateEmail('user@example.com');
if (result.valid) {
console.log(`Valid email: ${result.value}`);
} else {
console.error(`Error: ${result.error}`);
}
TypeScript 中 unknown
与 any
的深度解析与对比
特性 | any 类型 | unknown 类型 |
---|---|---|
类型安全性 | 无任何类型检查 | 强制类型检查 |
赋值操作 | 可赋值给任何类型 | 只能赋值给 any 或 unknown |
属性访问 | 允许直接访问 | 禁止直接访问 |
函数调用 | 允许直接调用 | 禁止直接调用 |
实例化 | 允许实例化 | 禁止实例化 |
操作符使用 | 允许所有操作 | 仅允许 == 、=== 、!= 、!== |
类型收缩必要性 | 不需要 | 必需 |
设计目的 | 兼容 JavaScript 动态类型 | 安全的顶级类型 |
详细对比分析
1. 类型安全级别
any
- 无类型安全
typescript
let unsafe: any;
// 允许任何操作 - 编译通过,但可能运行时出错
unsafe.toUpperCase(); // 可能不是字符串
unsafe(); // 可能不是函数
new unsafe(); // 可能不是构造函数
unsafe[0] = 10; // 可能不是数组
unknown
- 强制类型安全
typescript
let safe: unknown;
// 禁止任何操作 - 编译时报错
// safe.toUpperCase(); // ❌ 错误
// safe(); // ❌ 错误
// new safe(); // ❌ 错误
// safe[0] = 10; // ❌ 错误
// 必须进行类型检查
if (typeof safe === 'string') {
safe.toUpperCase(); // ✅ 安全
}
2. 赋值能力对比
赋值给变量
typescript
let anyValue: any;
let unknownValue: unknown;
// any 可以赋值给任何类型
let str1: string = anyValue; // ✅
let num1: number = anyValue; // ✅
// unknown 只能赋值给 any 或 unknown
let str2: string = unknownValue; // ❌ 错误
let num2: number = unknownValue; // ❌ 错误
let anyVar: any = unknownValue; // ✅
let unknownVar: unknown = unknownValue; // ✅
3. 类型收缩机制
unknown
必需的类型收缩
typescript
function processValue(value: unknown) {
// 类型收缩方式 1: typeof
if (typeof value === 'string') {
value.toUpperCase();
}
// 类型收缩方式 2: instanceof
if (value instanceof Date) {
value.getFullYear();
}
// 类型收缩方式 3: 自定义类型保护
if (isUser(value)) {
console.log(value.name);
}
// 类型收缩方式 4: 类型断言 (谨慎使用)
const str = value as string;
str.trim();
}
// 自定义类型保护
function isUser(obj: unknown): obj is { name: string } {
return !!obj && typeof obj === 'object' && 'name' in obj;
}
4. 联合与交叉类型行为
联合类型行为
typescript
// any 在联合类型中保持原样
type AnyUnion = any | string | number; // any
// unknown 在联合类型中吸收所有类型
type UnknownUnion = unknown | string | number; // unknown
交叉类型行为
typescript
// any 在交叉类型中吸收所有类型
type AnyIntersection = any & string; // any
// unknown 在交叉类型中被吸收
type UnknownIntersection = unknown & string; // string
5. 特殊类型关系
never
是 unknown
的子类型
typescript
type isNever = never extends unknown ? true : false; // true
keyof unknown
等于 never
typescript
type Keys = keyof unknown; // never
// 无法获取 unknown 的键
function getKeys(obj: unknown) {
// return Object.keys(obj); // ❌ 错误
}
6. 操作限制
仅允许相等性检查
typescript
let un1: unknown = 0;
let un2: unknown = 1;
un1 === un2; // ✅ 允许
un1 !== un2; // ✅ 允许
un1 + un2; // ❌ 错误:不允许算术操作
un1 * un2; // ❌ 错误
禁止其他操作
typescript
let un: unknown = { name: '10' };
un.name; // ❌ 错误:不能访问属性
un.trim(); // ❌ 错误:不能调用方法
let fn: unknown = function() {};
fn(); // ❌ 错误:不能调用函数
new fn(); // ❌ 错误:不能实例化
7. 最佳实践指南
优先使用 unknown
typescript
// 安全处理 JSON 解析
function safeParse(json: string): unknown {
try {
return JSON.parse(json);
} catch {
return null;
}
}
const user = safeParse('{"name": "Alice"}');
if (user && typeof user === 'object' && 'name' in user) {
console.log(user.name); // 安全访问
}
谨慎使用 any
typescript
// 仅在与无类型库交互时使用
declare const legacyLib: any;
legacyLib.doSomething();
结论总结
安全性级别:
any
:完全绕过类型系统(危险)unknown
:强制类型检查(安全)
使用场景:
any
:遗留代码迁移、快速原型开发unknown
:处理动态数据(API响应、用户输入)
类型收缩必要性:
unknown
必须通过类型保护或断言收缩类型any
不需要任何类型收缩
渐进迁移策略:
遗留代码 → any → unknown → 具体类型
类型安全优先级:
具体类型 > unknown > any
在 TypeScript 项目中,应始终优先选择 unknown
而不是 any
,以保持类型系统的安全性。只有在特殊情况下(如与无类型 JavaScript 库交互或遗留代码迁移)才考虑使用 any
,并且应尽快将其替换为更具体的类型。
交叉类型
- 交叉类型(Intersection Types)是将多个类型合并为一个类型
- 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
ts
interface Bird {
name: string,
}
interface Person {
age: number,
}
type BirdPerson = Bird & Person;
let p: BirdPerson = { name: 'hl',age:10 };
联合类型的交叉类型
- 联合类型的交叉类型取相交的类型
- 如果没有相交的类型则是
never
类型
ts
type Ta = string | number;
type Tb = number | boolean;
type Tc = Ta & Tb;
let a:Tc = 10; // ok
let b:Tc = 'a'; // Error
//
type Ta = string;
type Tb = boolean;
type Tc = Ta & Tb;
let a:Tc = '1' // Type 'string' is not assignable to type 'never'
typeof
- 可以获取一个变量的类型
ts
//先定义类型,再定义变量
type People = {
name:string,
age:number,
gender:string
}
let p1:People = {
name:'hl',
age:10,
gender:'male'
}
//先定义变量,再通过typeof定义类型
let p1 = {
name:'hl',
age:10,
gender:'male'
}
type People = typeof p1;
function getName(p:People):string{
return p.name;
}
getName(p1);
keyof
- 索引类型查询操作符
ts
interface Person{
name:string;
age:number;
gender:'male'|'female';
}
//type PersonKey = 'name'|'age'|'gender';
type PersonKey = keyof Person;
function getValueByKey(p:Person,key:PersonKey){
return p[key];
}
let val = getValueByKey({name:'zhufeng',age:10,gender:'male'},'name'); // key => 'name'|'age'|'gender';
console.log(val);
映射类型(接口中的属性都变成可选的)
- 在定义的时候用in操作符去批量定义类型中的属性
ts
nterface Person{
name:string;
age:number;
gender:'male'|'female';
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?:Person[Key]
}
let p1:PartPerson={};
//也可以使用泛型
type Part<T> = {
[key in keyof T]?:T[key]
}
let p2:Part<Person>={};
通过key的数组获取值的数组
- K extends keyof T,K只能是T的子项
ts
function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map((n) => o[n]);
}
let user = { id: 1, name: 'hl' };
type User = typeof user;
const res = pick<User, keyof User>(user, ["id", "name"]);
console.log(res);
条件类型
- 定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活
ts
interface Fish {
name: string
}
interface Water {
name: string
}
interface Bird {
name: string
}
interface Sky {
name: string
}
//若 T 能够赋值给 Fish,那么类型是 Water,否则为 Sky
type Condition<T> = T extends Fish ? Water : Sky;
let condition: Condition<Fish> = { name: '水' };
条件类型的分发
ts
interface Fish {
fish: string
}
interface Water {
water: string
}
interface Bird {
bird: string
}
interface Sky {
sky: string
}
//naked type
type Condition<T> = T extends Fish ? Water : Sky;
// (Fish extends Fish ? Water : Sky) | (Bird extends Fish ? Water : Sky)
// Water|Sky
let condition1: Condition<Fish | Bird> = { water: '水' };
let condition2: Condition<Fish | Bird> = { sky: '天空' };
找出T类型中U不包含的部分
ts
//never会被自动过滤
type Diff<T, U> = T extends U ? never : T;
type R = Diff<"a" | "b" | "c" , "a" | "b">; // "c"
let diff:R = 'c'
type Diff1<T, U> = T extends U ? T : never;
type R1 = Diff1<"a" | "b" | "c" , "a" | "b">; // "a" | "b"
let diff1:R1 = 'a'
diff1 = 'b'
Exclude(T排除U)
ts
type Exclude1<T,U> = T extends U ? never : T
let age:Exclude1<string|number,string> = 10
Exclude(T提取U)
ts
type Extract1<T,U> = T extends U ? T : never
let username:Extract1<string|number,string> = 'hl'
NonNullable
- 从 T 中排除 null 和 undefined
ts
type NonNullable<T> = T extends null | undefined ? never : T;
type E = NonNullable<string|number|null|undefined>;
let e:E = null; // Error
ReturnType
- infer最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量
- 获取函数类型的返回类型
ts
function getUserInfo() {
return { name: "hl", age: 10 };
}
// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
// type UserInfo = {
// name: string;
// age: number;
// }
type UserInfo = ReturnType<typeof getUserInfo>;
// ReturnType源码实现
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
function getUserInfo() {
return { name: "hl", age: 10 };
}
type UserInfo = ReturnType<typeof getUserInfo>;
const userA: UserInfo = {
name: "zhufeng",
age: 10
};
Parameters
- 构造函数类型T的形参类型的元组类型
ts
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
type T2 = Parameters<(<T>(arg: T) => T)>; // [unknown]
InstanceType
- 获取构造函数类型的实例类型
ts
type Constructor = new (...args: any[]) => any;
type ConstructorParameters<T extends Constructor> = T extends new (...args: infer P) => any ? P : never;
type InstanceType<T extends Constructor> = T extends new (...args: any[]) => infer R ? R : any;
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName() { console.log(this.name) }
}
//构造函数参数
type constructorParameters = ConstructorParameters<typeof Person>;
let params: constructorParameters = ['zhufeng']
//实例类型
type Instance = InstanceType<typeof Person>;
let instance: Instance = { name: 'zhufeng', getName() { } };
Partial(属性可选)
- Partial 可以将传入的属性由非可选变为可选
ts
type Partial<T> = { [P in keyof T]?: T[P] };
interface IUser{
name:string
age:number
}
let user: IUser ={name:'hl',age:10}
let user1: Partial<IUser> ={name:'hl'}
Required
- Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
ts
type Required<T> ={[ P in keyof T]-?:T[P]}
interface IUser{
name?:string
age?:number
}
let user: Required<IUser> ={name:'hl',age:19}
Readonly
- Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。
ts
type Readonly<T> ={readonly [ P in keyof T]:T[P]}
interface IUser{
name:string
age:number
}
let user: Readonly<IUser> ={name:'hl',age:19}
user.age = 10 // Cannot assign to 'age' because it is a read-only property.(2540)
Pick(保留)
- Pick 能够帮助我们从传入的属性中摘取某一项返回
ts
type Pick1<T,K extends keyof T> ={[P in K]:T[P]}
interface IUser {
name: string;
age: number;
add:string
}
// K 必须是T内部的属性
// K 只能是'name'|'age'|'add'
let user:Pick1<IUser,'name'|'age'> = {name:'hl',age:10}
Omit(剔除)
- Exclude 的作用是从 T 中排除出可分配给 U的元素.
- Omit<T, K>的作用是忽略T中的某些属性
- Omit = Exclude + Pick
ts
type Pick1<T,K extends keyof T> ={[P in K]:T[P]}
type SetDifference<A, B> = A extends B ? never : A;
type Omit<T, K extends keyof any> = Pick<T, SetDifference<keyof T, K>>;
let user:Omit<IUser,'age'> = {name:'hl'}
Record
- Record 是 TypeScript 的一个高级类型
- 他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
ts
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type Point = 'x' | 'y';
type PointList = Record<Point, { value: number }>
const cars: PointList = {
x: { value: 10 },
y: { value: 20 },
}
Mutable
- 将 T 的所有属性的 readonly 移除
ts
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}
Merge
- Merge<O1, O2>的作用是将两个对象的属性合并:
- Merge<O1, O2> = Compute + Omit<U, T>
ts
type O1 = {
id: number;
name: string;
};
type O2 = {
id: number;
age: number;
};
//Compute的作用是将交叉类型合并
type Compute<A extends any> = A extends Function ? A : { [K in keyof A]: A[K] };
type R1 = Compute<{ x: "x" } & { y: "y" }>;
type Merge<O1 extends object, O2 extends object> = Compute<
O1 & Omit<O2, keyof O1>
>;
type R2 = Merge<O1, O2>;
类型声明
- 声明文件可以让我们不需要将JS重构为TS,只需要加上声明文件就可以使用系统
- 类型声明在编译的时候都会被删除,不会影响真正的代码
- 关键字 declare 表示声明的意思,我们可以用它来做出各种声明
ts
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
普通类型声明
ts
declare let name: string; //变量
declare let age: number; //变量
declare function getName(): string; //方法
declare class Animal { name: string } //类
console.log(name, age);
getName();
new Animal();
export default {};
// 声明jQuery对象
declare const $: (selector: string) => { //变量
click(): void;
width(length: number): void;
};
$('#root').click();
console.log($('#root').width);