如何让你的TypeScript看起来更优雅
如何让你的TypeScript看起来更优雅
TypeScript现在已经成为我们浏览器客户端开发的利器,它的目的是帮助开发人员编写可维护的定代码,提供编码效率。
我相信大多数的人是使用JavaScript然后直接去使用TypeScript,没有深入理解TypeScript就直接上手,导致在处理类型的时候很难下手。写着写着就会变成AnyScript。
在本文中,我们将探讨 TypeScript 的一些高级特性,并展示它们如何帮助您编写更高质量的代码。无论您是刚开始接触 TypeScript,还是希望深入了解其高级功能,本文都将为您提供宝贵的见解和实用的建议。
模板字面量类型
模板字面量类型是 TypeScript 中的一种高级类型特性,它允许我们使用字符串模板语法创建复杂的字符串组合类型。让我们通过一个例子来理解这一点:
假设我们在一个应用程序中有不同的用户角色和权限级别,并且我们想创建一个表示角色和权限级别组合的类型。
type Role = "admin" | "user" | "guest";
type PermissionLevel = "read" | "write" | "execute";
type RolePermission = `${Role}-${PermissionLevel}`;
let rolePermission: RolePermission = "admin-read"; // Valid
通过使用模板字面量类型,我们可以创建 RolePermission
类型。它将 Role
和 PermissionLevel
的每个可能值组合起来,生成九种可能的字符串类型:"admin-read", "admin-write", "admin-execute", "user-read", "user-write", "user-execute", "guest-read", "guest-write",以及 "guest-execute"。
例如,"manager-read" 不在 RolePermission
的定义范围内,因为 Role
类型中不包含 "manager"。因此,TypeScript 会抛出一个错误:Type "manager-read" is not assignable to type 'RolePermission'
。
通过使用模板字面量类型,我们可以轻松创建和管理复杂的字符串组合类型,从而提高代码的可读性和类型安全性。这在需要定义和验证复杂字符串模式的场景中特别有用。
使用 TypeScript 类型谓词进行精确的类型检查
类型谓词是一种强大的工具,用于在运行时检查并确保变量属于特定类型。通过使用类型谓词,我们可以在编写类型安全的代码时实现更精确的类型检查,从而避免类型错误,增强代码的健壮性和可维护性。
假设我们有一个表示动物的联合类型,包括猫(Cat)和狗(Dog):
interface Cat {
kind: "cat";
meow: () => void;
}
interface Dog {
kind: "dog";
bark: () => void;
}
type Animal = Cat | Dog;
现在,我们想要编写一个函数来检查某个 Animal 是否属于 Cat 类型。此时,我们可以使用类型谓词:
function isCat(animal: Animal): animal is Cat {
return animal.kind === "cat";
}
function makeSound(animal: Animal) {
if (isCat(animal)) {
animal.meow(); // TypeScript 知道 animal 是 Cat 类型。 Ide也知道更好的提示
} else {
animal.bark(); // TypeScript 知道 animal 是 Dog 类型。 Ide也知道更好的提示
}
}
通过这样做,makeSound 函数可以准确识别 Animal 的具体类型,并在相应的条件分支中调用 Cat 或 Dog 特有的方法。这不仅增强了代码的类型安全性,还使代码更清晰、更易于维护。
索引访问类型
索引访问类型使用语法 T[K],允许我们访问类型 T 中与键 K 相关联的类型。这类似于在 JavaScript 中使用方括号访问对象属性,但在 TypeScript 中,索引访问类型提供了编译时类型检查。
假设我们有一个包含数据和错误信息的 API 响应类型:
interface ApiResponse<T> {
data: T;
error: string | null;
}
interface Product {
id: number;
name: string;
price: number;
}
type ProductResponse = ApiResponse<Product>;
我们可以使用索引访问类型来提取 ProductResponse 类型中 data 属性的类型。
type ProductDataType = ProductResponse['data']; // 再Vscode中可以识别出Product类型
在实际应用中,我们经常需要根据属性名称动态访问对象属性并进行类型保护。通过使用索引访问类型和 keyof
操作符,我们可以实现这一点:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
const userId = getProperty(user, 'id'); // number
const userName = getProperty(user, 'name'); // string
const userEmail = getProperty(user, 'email'); // string
在这个例子中:
- getProperty 函数接收一个对象 obj 和一个属性名 key,并返回该对象中该属性的值。
- T 是对象的类型,K 是属性名的类型(必须是 T 的键)。
- 返回类型 T[K] 代表对象 T 中对应键 K 的属性类型。
TypeScript 中的工具类型
TypeScript 提供了许多内置的工具类型,帮助开发者在各种场景下快速生成和操作复杂类型。通过使用这些工具类型,可以显著提高开发效率,减少手动编写类型定义的工作量��并增强代码的可读性和可维护性。以下是一些工具类型的例子:
Partial 类型用于将类型 T 的所有属性变为可选属性。当你需要构建一个对象,但最初不需要所有属性时,它非常有用。
interface User {
id: number;
name: string;
email: string;
}
// 等价于快速变成
//interface User {
// id?: number;
// name?: string;
// email?: string;
//}
function updateUser(id: number, update: Partial<User>) {
// ...
}
updateUser(1, { name: "Alice" }); // Valid
updateUser(2, { email: "bob@example.com" }); // Valid
Required是把一个类型T的所有属性变成必填
interface User {
id?: number;
name?: string;
email?: string;
}
const completeUser: Required<User> = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
Readonly是把一个类型T的所有属性变成可读
interface User {
id: number;
name: string;
email: string;
}
const user: Readonly<User> = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
Pick 类型用于通过从类型 T 中选择一部分属性来创建一个新类型。当你需要快速基于某个类型快速定义一个新类型的时候很有用。
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserSummary = Pick<User, "id" | "name">;
const userSummary: UserSummary = {
id: 1,
name: "Alice"
};
Omit 类型用于通过从类型 T 中排除指定属性来创建新类型。
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserWithoutEmail = Omit<User, "email">;
const userWithoutEmail: UserWithoutEmail = {
id: 1,
name: "Alice",
age: 30
};
在实际开发中,我们经常需要结合多种工具类型来创建复杂的类型定义,以满足特定需求。
interface User {
id: number;
name: string;
email: string;
age?: number;
}
type ReadonlyPartialUser = Readonly<Partial<User>>;
const user: ReadonlyPartialUser = {
id: 1,
name: "Alice"
};
user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
TypeScript类型推断
利用 TypeScript 的高级类型推断TypeScript 的高级类型推断机制是其类型系统的核心功能之一。通过类型推断,TypeScript 可以自动推断变量、函数返回值和表达式的类型,减少显式类型注解的需求,使代码更加简洁和优雅。下面,我们将介绍一些高级类型推断技术和示例,展示如何利用这些特性来提高代码质量和可读性。
类型推断的基础
TypeScript 可以在很多情况下自动推断类型。
例如,当你声明一个变量并赋值时,TypeScript 会根据赋值推断变量的类型:
let x = 42; // TypeScript 推断 x 的类型为 number
let y = "Hello, TypeScript!"; // TypeScript 推断 y 的类型为 string
当你定义一个函数并返回一个值时,TypeScript 会自动推断函数的返回类型:
function add(a: number, b: number) {
return a + b; // TypeScript 推断函数的返回类型为 number
}
这种推断机制使代码更加简洁,不需要显式指定函数的返回类型。
高级类型推断示例
推断对象属性类型:TypeScript 可以根据对象字面量自动推断属性的类型
const user = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
// TypeScript 推断 user 的类型为 { id: number; name: string; email: string; }
推断数组元素类型:TypeScript 可以根据数组的元素推断数组的类型
const numbers = [1, 2, 3, 4]; // TypeScript 推断 numbers 的类型为 number[]
const names = ["Alice", "Bob", "Charlie"]; // TypeScript 推断 names 的类型为 string[]
推断泛型类型:使用泛型时,TypeScript 可以根据传递的参数推断泛型的具体类型:
function identity<T>(value: T): T {
return value;
}
const numberIdentity = identity(42); // TypeScript 推断 T 为 number
const stringIdentity = identity("Hello"); // TypeScript 推断 T 为 string
条件类型推断:TypeScript 支持条件类型,可以根据不同条件推断不同的类型
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
推断函数参数类型:使用高阶函数时,TypeScript 可以推断回调函数的参数类型
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2); // TypeScript 推断 n 的类型为 number
实际应用中的高级类型推断
在实际项目中,利用 TypeScript 的高级类型推断可以使代码更加简洁和具表现力。以下是一个综合示例,展示如何在实际开发中应用这些推断技术:
interface User {
id: number;
name: string;
email: string;
}
function getUser(id: number): User {
return {
id,
name: "User" + id,
email: `user${id}@example.com`
};
}
const users = [getUser(1), getUser(2), getUser(3)];
function sendEmail(user: User, message: string) {
console.log(`Sending email to ${user.email}: ${message}`);
}
users.forEach(user => sendEmail(user, "Welcome!")); // TypeScript 推断 user 的类型为 User
TypeScript 提供了一系列强大的功能,使我们能够编写更优雅和高效的代码。通过类型谓词,我们可以实现精确的类型检查,确保在不同类型之间安全地进行类型转换;使用索引访问类型,我们可以动态地操作和访问复杂类型;工具类型简化了类型定义过程,增强了代码的可读性和可维护性;而高级类型推断使 TypeScript 能够自动推断变量和表达式的类型,减少显式类型注解的需求。
这些功能不仅提高了开发效率,还增强了代码的类型安全性和可维护性。在实际开发中,充分利用这些 TypeScript 特性将帮助我们编写更加清晰、简洁和健壮的代码。通过不断探索和应用 TypeScript 的强大功能,我们可以在项目中实现更高质量的代码和更高效的开发流程。
本文使用 markdown.com.cn 排版
转载自:https://juejin.cn/post/7393293636653006867