TypeScript 类型守卫
Source: Dev.to
请提供您希望翻译的正文内容,我将为您翻译成简体中文。
当你构建支付系统时,“差不多就行”并不够好
单个 undefined 值或不匹配的对象属性可能决定交易是成功还是让客户感到沮丧(或导致失单)。
TypeScript 的类型守卫 是你的第一道防线。它们让你将宽泛、不确定的类型缩小为可以安全交互的特定类型。在本指南中,我们将构建一个迷你支付处理器,并学习如何使用类型守卫使其防崩溃。
1. 问题:JavaScript 的“静默失败”
想象一下,你有一个函数用于处理不同类型的支付响应。在普通的 JavaScript 中,你可能会这样写:
function processResponse(response) {
// If response is a 'Success' object, it has a 'transactionId'
// If it's an 'Error' object, it has a 'message'
console.log("Payment successful! ID: " + response.transactionId);
}
// What if the API returned an error?
processResponse({ message: "Insufficient funds" });
// Output: "Payment successful! ID: undefined"JavaScript 不会报错;它只会返回 undefined。这就是 静默失败。如果我们知道如何 缩窄 类型,TypeScript 可以帮助捕获它。
2. 内置守卫:基础
TypeScript 提供了内置的运算符来执行运行时检查。当 TypeScript 检测到这些检查时,它会 缩小 该代码块其余部分的类型。
typeof(检查原始类型)
在我们的支付应用中,amount 可能是 number 或 string(如果它来自表单输入)。
function formatAmount(amount: string | number) {
if (typeof amount === "string") {
// TypeScript 知道此处 `amount` 是字符串。
// 我们可以安全地调用字符串特有的方法。
return parseFloat(amount).toFixed(2);
}
// 在这里 TypeScript 知道 `amount` 必须是数字。
return amount.toFixed(2);
}instanceof(检查类实例)
假设你有不同的类用于 CreditCard 和 GiftCard 支付。每个类都有各自的验证逻辑。
class CreditCard {
verifyCVV() {
return true;
}
}
class GiftCard {
checkBalance() {
return 50.0;
}
}
function verifyPayment(method: CreditCard | GiftCard) {
if (method instanceof CreditCard) {
// 可以安全调用 CreditCard 的方法
method.verifyCVV();
} else {
// 可以安全调用 GiftCard 的方法
method.checkBalance();
}
}3. 自定义类型谓词: “守门员”
有时简单的检查并不足够。您可能需要一个可复用的函数来判断付款是否 complete(已完成)。这时 Custom Type Predicate 就派上用场了。
interface Payment {
id: string;
status: "pending" | "completed" | "failed";
}
interface CompletedPayment extends Payment {
status: "completed";
confirmedAt: Date;
}
/* This is our Custom Type Predicate */
function isCompleted(payment: Payment): payment is CompletedPayment {
return payment.status === "completed";
}
const myPayment: Payment = { id: "123", status: "completed" };
if (isCompleted(myPayment)) {
// TypeScript now knows `myPayment` has a `confirmedAt` property!
console.log(myPayment.confirmedAt);
}4. 判别联合:黄金标准
如果你只能从本文学到一种模式,就选这个。通过在你的类型中添加一个单一的公共属性(判别符),你可以获得 100 % 的类型安全和完美的 IDE 自动补全。
interface CardPayment {
type: "card"; // Discriminator
lastFour: string;
}
interface PayPalPayment {
type: "paypal"; // Discriminator
email: string;
}
type PaymentMethod = CardPayment | PayPalPayment;
function getReceipt(method: PaymentMethod) {
switch (method.type) {
case "card":
return `Charged card ending in ${method.lastFour}`;
case "paypal":
return `Charged PayPal account: ${method.email}`;
}
}为什么这是一种超能力: 如果你后来添加了 Bitcoin 支付类型,却忘记更新 switch,TypeScript 会立即标记错误。这就像有一位高级开发者在你肩上监督一样。
5. 反模式:“前后对比”
作为初学者,你可能会倾向于使用快捷方式来消除红色错误提示。下面看看为什么不应该这样做。
“不安全的断言”
Before(“脏”做法):
// Bypassing safety with `as any`
const receipt = (payment as any).cardNumber;
// If `payment` is actually a Bank Transfer, this is `undefined`.After(“防护”做法):
if ("cardNumber" in payment) {
const receipt = payment.cardNumber; // Safe and verified
}使用 in 运算符在处理来自外部 API 的不可预测数据时充当 盾牌。
摘要:你的类型守卫决策树
不确定该使用哪种守卫?请参考以下检查表:
| 场景 | 使用的守卫 |
|---|---|
| 简单原始类型(string、number、boolean 等) | typeof |
使用 new MyClass() 创建的对象 | instanceof |
类型共享共同属性,如 type 或 status | 判别联合(适用于复杂逻辑) |
| 检查“混乱”对象上的特定属性 | in 运算符 |
你想要一个可复用的函数来简化 if 语句 | 自定义类型谓词 |
使用正确的守卫,你的支付处理系统崩溃的可能性会大大降低——并且更有可能让客户满意。
谓词 (is)
结论
类型守卫弥合了 TypeScript 严格规则与 JavaScript 灵活现实之间的鸿沟。通过在支付系统中实现这些模式,你不仅仅是在编写代码——你正在构建一个可靠、可预测的引擎,轻松处理所有边缘情况。
祝编码愉快!
Reference:
Mastering TypeScript Type Guards — Better Stack Community