摘要 :本文系统梳理 TypeScript 中几类进阶类型技巧:先介绍
npx工具链的用法,再讲解命名空间与函数/类的合并特性;随后深入类型断言(as const、satisfies)与自定义类型守卫(is);接着进入类型编程,涵盖条件类型与infer提取、映射类型修饰符、模板字面量类型及this类型声明;最后介绍泛型的约束、默认类型与分布式条件类型。全文以可运行的代码示例为主,帮助读者快速掌握这些提升类型安全与开发效率的实用能力。
TS 文件编译
npx = Node Package eXecute,是 npm 自带(npm 5.2+ 起内置)的命令行工具,用于执行 npm 包里的可执行文件。
-
npx tsc xxx.ts临时使用,无需全局安装; -
tsx xxx.ts需要全局安装(npm install -g tsx)。npx tsc xxx.ts --outDir dist # 编译 node dist/xxx.js # 需要安装 npm install typescript
npx tsx xxx.ts # 用 esbuild 即时转译 + 运行 //开发环境经常使用
npx ts-node xxx.ts # 用 tsc 类型检查 + 即时编译
npx tsc --noEmit # 要类型检查时使用
1. 命名空间与函数/类合并
TypeScript 允许同名的接口或命名空间自动合并,这是它独有的特性。
function Counter() {}
namespace Counter {
export let count = 0;
export function increment() { count++; }
}
Counter()
Counter.increment();
console.log(Counter.count); // 有类型提示
2. 类型断言与修饰符
2.1. as const 常量断言
将对象或数组变为完全只读,并将类型收窄为最具体的字面量类型。
let arr = [1, 2, 3] as const; // 类型变为 readonly [1, 2, 3],不能 push
let obj = { name: 'Tom', age: 18 } as const;
// 类型变为 { readonly name: "Tom"; readonly age: 18; }
2.2 satisfies 操作符(TS 4.9+)
确保变量满足某个类型,同时不丢失原始类型的具体信息(这是与 as 断言最大的区别)。
type Colors = 'red' | 'blue';
type ColorMap = Record<Colors, string>;
// 使用 as 会丢失具体 key 的信息
const badMap = { red: '#f00', blue: '#00f', green: '#0f0' } as ColorMap; // 不报错 但 key被收窄
// 使用 satisfies 既检查类型,又保留具体字面量
const goodMap = { red: '#f00', blue: '#00f', green: '#0f0' } satisfies ColorMap;// ❌ 报错,对象字面量只能指定已知属性,并且"green"不在类型"ColorMap"中。
// goodMap.green 仍然可以访问,且类型推断为 string
2.3 自定义 类型守卫(is 关键字)
/**
* `val is string` 是 TypeScript 的 类型谓词语法 。
* 它告诉编译器:
* - 当`isString(val)` 返回`true` 时,`val` 的类型会被收窄为`string` 。
* @param val 要检查的值
* @returns 如果值是字符串则返回 true,否则返回 false
*/
function isString(val: any): val is string {
return typeof val === 'string';
}
function printLength(val: string | number) {
if (isString(val)) {
console.log(val.length); // TS 知道这里 val 是 string
}
}
单纯typeof val === 'string' 也能在 TS 中触发类型收窄。isString 这类封装的价值在于:
-
作为 可复用函数 在多处调用,避免重复写
typeof -
可以组合更复杂的判断(如
isString+ 非空 + 符合某种格式)function isNonEmptyString ( val : any ): val is string {
return typeof val === 'string' && val. length > 0 ;
}/**
- 编译器是不会怀疑的
- 你说是就是吧
- 你可以调用字符串的方法
*/
function isErrorString(val: any): val is string {
return typeof val === 'number';
}
3. 类型编程
3.1 条件类型与 infer 提取
在类型声明中使用三元运算,并通过 infer 推断内部类型,这里有个词语 "类型可分配性检查" (assignability check),意思是:T extends Promise<infer U> T 是否赋值给 Promise<infer U>结构类型。
注意:extends 在这里的作用是 模式匹配 + 类型提取
// 提取 Promise 里的返回值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
//结构兼容 Promise(有 then 方法),即使没"继承"Promise
interface Thenable {
then<U>( onfulfilled : ( v : any ) => U): Promise <U>;
}
type C = UnwrapPromise < number >; // number(走 false 分支)
3.2 映射类型与修饰符(+ / -)
在映射类型中,可以添加或移除 readonly 和 optional 修饰符
type Mutable<T> = {
-readonly [K in keyof T]: T[K]; // 移除 readonly
};
type RequiredFields<T> = {
[K in keyof T]-?: T[K]; // 移除 ? (变为必选)
};
type ReadonlyPartial<T> = {
+readonly [K in keyof T]+?: T[K]; // 添加 readonly 和 ? (默认就是加,+可省略)
};
// 示例
const user: ReadonlyPartial<{
name: string;
age: number;
}> = {
name: '张三',
age: 18,
};
user.age = 20; // 报错,因为 age 是 readonly 类型
3.3 模板字面量类型(Template Literal Types)
像写 JS 模板字符串一样声明类型
type HttpMethod = 'get' | 'post' | 'put' | 'delete';
type ApiEndpoint = `/api/${HttpMethod}/${string}`;
const url: ApiEndpoint = '/api/get/users'; // 正确
const badUrl: ApiEndpoint = '/api/head/users'; //类型报错
3.4 this 类型声明
函数参数列表中第一个位置声明 this 的类型(它不会生成实际 JS 参数)
function greet(this: { name: string }, msg: string) {
console.log(msg);
return `${this.name}: ${msg}`;
}
const obj = { name: 'Tom', greet };
obj.greet('Hello'); // this 指向 obj
//greet('Hello'); // 直接调用会报错,因为 this 不是预期类型
方案2
interface MyObject {
name: string;
logName(): void;
}
const obj2: MyObject & ThisType<MyObject> = {
name: '李四',
logName() {
console.log(this.name); // this 被推断为 { name: string }
}
};
obj2.logName(); // this 指向 obj2
4 泛型
4.1 泛型约束与默认类型
// T 必须继承 object,且默认是空对象
type Config<T extends object = {}> = {
data: T;
timestamp: number;
};
4.2 分布式条件类型
当泛型参数是联合类型时,条件类型会自动分发
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]