TypeScript 已迅速成为像我这样的开发人员的首选语言,他们希望通过添加类型安全、更好的工具和改进的可维护性来改进 JavaScript 代码库。
随着语言的发展和成熟,TypeScript 沿途获得了一些隐藏的功能。 在本文中,我们将深入探讨 10 个鲜为人知的技巧,它们将帮助您释放 TypeScript 的全部潜力。
1. 使用 keyof 和映射类型动态构建类型
keyof 关键字提供了一种基于现有类型的键动态构建类型的强大工具。
“keyof 运算符采用对象类型并生成其键的字符串或数字文字联合。”
— TypeScript 文档
结合映射类型,您可以从现有类型生成新类型,同时保留原始结构。
type Point = { x: number; y: number };
type NullablePoint = {
[K in keyof Point]: Point[K] | null;
};
在这里,NullablePoint 成为一种新类型,具有与 Point 相同的键,但具有可为空的值。
2. 灵活类型的条件类型
条件类型允许创建依赖于其他类型的复杂类型。 这可以导致更灵活和可重用的类型定义。
type Flatten<T> = T extends Array<infer U> ? U : T;
type FlattenedNumbers = Flatten<number[]>; // number
在此示例中,Flatten 类型检查提供的类型 T 是否为数组,如果是,则提取内部类型 U。
3. 类型柯里化的部分应用类型
TypeScript 对高阶类型的支持允许您创建部分应用的类型。 这种称为类型套用的技术可实现强大的组合模式。
type Curried<T, U> = (arg: U) => T & U;
function merge<T, U>(fn: Curried<T, U>): T & U {
return fn({} as U);
}
type UserDetails = { firstName: string; lastName: string };
type UserSettings = { theme: string; language: string };
const result = merge<UserDetails, UserSettings>(() => ({
firstName: 'John',
lastName: 'Doe',
theme: 'dark',
language: 'en',
}));
console.log(result);
/** {
"firstName": "John",
"lastName": "Doe",
"theme": "dark",
"language": "en"
} */
在此示例中,我们有两种不同类型的对象,UserDetails 和 UserSettings。 通过使用 merge 函数,我们可以将这两种类型组合成一个包含两种类型属性的对象。 当您想要创建一个新对象来组合来自多个来源的属性,同时仍保持 TypeScript 类型完整时,这会很有用。
当然,在 TypeScript 中还有其他方法可以实现这一点,例如,使用类型交集和直接扩展语法。 此示例的主要目的是演示类型柯里化的概念,当在正确的上下文中应用时,它可以成为一种强大的技术。
4. 编译时类型检查的类型保护
当您需要执行编译时类型检查时,类型保护很有用。 它们允许 TypeScript 缩小特定代码块中值的类型。
function isString(value: any): value is string {
return typeof value === "string";
}
function concat(a: unknown, b: unknown) {
if (isString(a) && isString(b)) {
return a.concat(b);
}
}
在 concat 函数中,TypeScript 知道 a 和 b 是字符串,因为类型保护是 isString。 这也可以改进 VS Code Intellisense 的提示,因为编译器将能够约束任何类型。
5. 带返回类型的高级类型推断
Return Type 实用程序类型可以推断函数的返回类型,从而更容易使用高阶函数及其类型。
type MyFunction = (x: number, y: number) => { result: number };
type MyFunctionReturnType = ReturnType<MyFunction>; // { result: number }
在这里,MyFunctionReturnType 成为 MyFunction 的推断返回类型。
6. 递归类型的类型级编程
TypeScript 支持递归类型,允许您创建复杂的类型级计算和转换。
type TupleToUnion<T extends any[]> = T[number];
type MyTuple = [string, number, boolean];
type MyUnion = TupleToUnion<MyTuple>; // string | number | boolean
在此示例中,TupleToUnion 将元组类型转换为联合类型,这在处理复杂数据结构时非常有用。
就个人而言,我更喜欢联合类型而不是枚举,以至于我会将我看到的任何枚举重构为联合类型。 智能感知更好!
7. 不可变类型只读
TypeScript 提供了一个内置的 Readonly 实用程序类型,它使对象的所有属性都成为只读的。 当您想在代码库中强制执行不变性时,这很有用。
type User = { name: string; age: number };
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { name: "Alice", age: 30 };
user.age = 31;
// Error: Cannot assign to 'age' because it is a read-only property
通过使用 Readonly 实用程序,我们确保用户对象不会发生变化。
8. 类型断言以获得更多控制
当您对值的类型的了解比 TypeScript 的类型推断所能推断的更多时,类型断言很有用。 它们允许您为值指定更精确的类型,而无需执行任何运行时检查。
const unknownValue: unknown = "hello world";
const stringValue: string = unknownValue as string;
在此示例中,我们断言 unknownValue 确实是一个字符串,TypeScript 将信任此断言。
9. 更安全常量的唯一符号
TypeScript 独特的符号类型可以创建独特的非字符串值,非常适合更安全的常量定义和避免名称冲突。
const MyConstant = Symbol("MyConstant") as unique symbol;
type MyType = {
[MyConstant]: string;
};
const obj: MyType = { [MyConstant]: "hello world" };
console.log(obj[MyConstant]); // "hello world"
在这里,MyConstant 是一个独特的符号,确保没有其他属性可以与它冲突。
10.代码组织的合并类型声明
TypeScript 允许合并类型声明,这在跨多个文件拆分类型定义或从外部库扩展类型时非常有用。
// file1.ts
interface Point {
x: number;
y: number;
}
// file2.ts
interface Point {
z: number;
}
// Merged Point type: { x: number; y: number; z: number }
通过在 file1.ts 和 file2.ts 中声明 Point,TypeScript 会将两个声明合并为一个类型。 我认为这种行为只发生在 interface 关键字上,而不是 type 关键字,所以我更喜欢 type。
这 10 个 TypeScript 提示和技巧应该可以帮助您提升 TypeScript 技能并编写更强大、灵活和可维护的代码。
不要害怕探索 TypeScript 的高级功能,并在您的项目中利用它们来实现更干净、更安全的代码。