”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 简化 TypeScript 中的类型缩小和防护

简化 TypeScript 中的类型缩小和防护

发布于2024-11-05
浏览:935

Simplifying Type Narrowing and Guards in TypeScript

Introduction to Narrowing Concept

Typescript documentation explains this topic really well. I am not going to copy and paste the same description here, rather I want to make it even simpler and shorter. Let’s look at a problem and see how the concept of narrowing helps us when coding in TypeScript.

Look at the code very carefully. The explanation is written below the code snippet.

function padLeft(padding: number | string, input: string): string {
    return " ".repeat(padding)   input;
}

If padding is a number, it will be treated as the number of spaces we want to prepend to input since padding passed through repeat means padding has to be a number value.

Now, the moment padLeft function is called, it throws an error which looks somewhat like this:

"Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'."

TypeScript is warning us that we’re passing a value with type number | string to the repeat function, which only accepts a number, and it’s right. In other words, we haven’t explicitly checked if padding is a number first, nor are we handling the case where it’s a string, so let’s do exactly that.

function padLeft(padding: number | string, input: string): string {
  if (typeof padding === "number") {
    return " ".repeat(padding)   input;
  }
  return padding   input;
}

When we write if (typeof padding === "number"), TypeScript knows we are checking if padding is a number. This kind of check is called a "type guard." TypeScript looks at the code and tries to figure out the exact type of a value. When TypeScript makes a type more specific through these checks, it’s called "narrowing."

Type Guards

Type Guards are runtime checks that allow TypeScript to infer a more specific type of a variable within a conditional block. TypeScript uses these checks to "guard" against invalid operations by ensuring that the variable has the expected type.

The most common built-in type guards in TypeScript are:

  • typeof: Checks if a variable is a primitive type (e.g., string, number, boolean, etc.)

  • instanceof: Checks if a variable is an instance of a class or constructor function.

There is an example of typeof in the previous section where you can see how typeof narrows down a union type to a specific one.

But here is an example of using instanceof:

class Dog {
  bark() {
    console.log("Woof!");
  }
}

class Cat {
  meow() {
    console.log("Meow!");
  }
}

function animalSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark(); // TypeScript knows `animal` is a `Dog` here
  } else {
    animal.meow(); // TypeScript knows `animal` is a `Cat` here
  }
}

In this example, instanceof is used to narrow the type of animal to either Dog or Cat. This allows us to safely call the appropriate method (bark() or meow()) based on the type of animal.

Truthiness narrowing

Truthiness might not be a word you’ll find in the dictionary, but it’s very much something you’ll hear about in JavaScript.

Truthiness narrowing in TypeScript refers to the way TypeScript refines types based on conditions (&&s, ||s, if statements, Boolean negations (!), and more) that check if a value is "truthy" or "falsy." Values like false, 0, null, undefined, NaN, and "" (empty string) are considered falsy, while all other values are truthy.

When you use an if statement, TypeScript automatically narrows the type by excluding falsy values from the possible types.

function printMessage(message: string | null) {
  if (message) {
    // TypeScript knows 'message' can't be null here, it's narrowed to 'string'
    console.log(message.toUpperCase());
  } else {
    console.log("No message provided.");
  }
}

printMessage("Hello");   // Output: HELLO
printMessage(null);      // Output: No message provided.

In the if (message) block, TypeScript narrows the type from string | null to just string, since null is falsy and won't pass the condition.

You can always convert a value to a boolean by using the Boolean function or by using !! (double negation). The !! method has an advantage: TypeScript understands it as a strict true or false, while the Boolean function just gives a general boolean type.

const value = "Hello";

// Using Boolean function
const bool1 = Boolean(value);  // type: boolean

// Using double negation (!!)
const bool2 = !!value;         // type: true

To be specific understand this:

  • bool1's value is a boolean type value, meaning it can be either true or false. TypeScript just knows it's a boolean, but it doesn't specify which one.

  • bool2's value is considered a literal type—either exactly true or exactly false, depending on the value of !!value. In this case, since "Hello" is truthy, bool2 will be of type true.

Equality Narrowing

Equality narrowing in TypeScript happens when TypeScript refines the type of a variable based on an equality check (like === or !==). This means that after the check, TypeScript can "narrow" the possible types of a variable because it now knows more about it.

function example(value: string | number) {
  if (typeof value === "number") {
    // Here, TypeScript knows value is a number
    console.log(value.toFixed(2));  // Safe to use number methods
  } else {
    // In this block, TypeScript narrows value to string
    console.log(value.toUpperCase());  // Safe to use string methods
  }
}

The in operator narrowing

The in operator narrowing in TypeScript helps us figure out if an object has a specific property, and it also helps TypeScript refine (narrow) the types based on that check.

When you use "propertyName" in object, TypeScript checks if the object (or its prototype) has the property. Based on whether the check is true or false, TypeScript can understand more about the type of that object.

  • If the check is true (the property exists), TypeScript narrows the object’s type to include the types that have this property (either required or optional).

  • If the check is false (the property doesn't exist), TypeScript narrows the type to exclude types that have this property.

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function speak(animal: Cat | Dog) {
  if ("meow" in animal) {
    // TypeScript now knows 'animal' must be a Cat
    animal.meow();
  } else {
    // TypeScript now knows 'animal' must be a Dog
    animal.bark();
  }
}

In the if ("meow" in animal) check, TypeScript checks if the animal has the meow method. If it does, TypeScript knows the animal is a Cat. If not, TypeScript knows it’s a Dog.

Using type predicates

Type predicates in TypeScript are a way to narrow down the type of a variable using a function that returns a boolean. They help TypeScript understand what type a variable is after the check.

A type predicate is written as parameterName is Type. When you use this in a function, TypeScript knows that if the function returns true, the parameter is of that specific type.

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function isFish(animal: Fish | Bird): animal is Fish {
  return (animal as Fish).swim !== undefined;  // Check if animal has a swim method
}

function move(animal: Fish | Bird) {
  if (isFish(animal)) {
    // TypeScript knows 'animal' is a Fish here
    animal.swim();
  } else {
    // TypeScript knows 'animal' is a Bird here
    animal.fly();
  }
}

Simple explanation:

  • The isFish function checks if the animal has a swim method.

  • If it does, the function returns true, and TypeScript understands that animal is a Fish.

  • If it doesn't, TypeScript knows it's a Bird.

Discriminated unions

Discriminated Unions are a pattern in TypeScript where you can use a common property (called a "discriminant") to differentiate between different object types in a union.

Look at the following example.

interface Car {
  kind: "car";
  drive: () => void;
}

interface Bike {
  kind: "bike";
  pedal: () => void;
}

type Vehicle = Car | Bike;

function operateVehicle(vehicle: Vehicle) {
  switch (vehicle.kind) {
    case "car":
      vehicle.drive(); // TypeScript knows `vehicle` is a `Car` here
      break;
    case "bike":
      vehicle.pedal(); // TypeScript knows `vehicle` is a `Bike` here
      break;
  }
}

Here, the kind property is the discriminant. TypeScript uses it to narrow the Vehicle type to either Car or Bike based on the value of kind.

Exhaustiveness checking

Exhaustiveness checking in TypeScript ensures that all possible cases of a union type are handled in your code. When you use type narrowing (like with if statements or switch cases), TypeScript checks if all types in a union have been considered. If any case is missing, TypeScript will give you an error, helping you catch potential bugs.

type Cat = { type: "cat"; meow: () => void };
type Dog = { type: "dog"; bark: () => void };
type Animal = Cat | Dog;

function makeSound(animal: Animal) {
  switch (animal.type) {
    case "cat":
      animal.meow();  // TypeScript knows 'animal' is Cat here
      break;
    case "dog":
      animal.bark();  // TypeScript knows 'animal' is Dog here
      break;
    default:
      // This will give an error if we add more animal types
      const _exhaustiveCheck: never = animal;
      throw new Error(`Unknown animal: ${animal}`);
  }
}

The Animal type is a union of Cat and Dog. In the makeSound function, we check the type property of animal.

Exhaustiveness Check:

  • If we handle both cat and dog, everything is fine.

  • If we later add another animal type, like Bird, and forget to update the switch statement, TypeScript will show an error at the default case. This error happens because the default case is assigned a never type, meaning it shouldn’t happen if all possible types are accounted for.

Other Ways of Type Narrowing

Assignments: when we assign to any variable, TypeScript looks at the right side of the assignment and narrows the left side appropriately.

let x = Math.random() 



Type narrowing with never type: The never type in TypeScript represents values that never occur. It is often used to indicate unreachable code, such as when a function always throws an error or has an infinite loop. When TypeScript recognizes a never type, it can narrow down types effectively.

function assertIsString(value: string | number) {
  if (typeof value !== "string") {
    // If value is not a string, throw an error
    throw new Error("Not a string!");
  }
  // Here, TypeScript knows 'value' is a string
  console.log(value.toUpperCase());
}

In the assertIsString function, if value is not a string, we throw an error. TypeScript understands that if it reaches the console.log, value must be a string. If it were anything else, the function would not complete normally, leading to a never type.

Last Words

I am sure you got the point of narrowing concept. We can have other possible ways to narrow down types. But I believe the explanation and knowledge you have gained so far from this article is more than enough to grasp the concept and utilize it. Let me know if this was beneficial for you.

版本声明 本文转载于:https://dev.to/abeertech01/simplifying-type-narrowing-and-guards-in-typescript-5gh5?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta:列偏移的删除和恢复Bootstrap 4 在其 Beta 1 版本中引入了重大更改柱子偏移了。然而,随着 Beta 2 的后续发布,这些变化已经逆转。从 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    编程 发布于2025-01-05
  • 尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    解决 PHP 中的 POST 请求故障在提供的代码片段中:action=''而不是:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"检查 $_POST数组:表单提交后使用 var_dump 检查 $_POST 数...
    编程 发布于2025-01-05
  • 如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    MySQL配置不正确:相对路径的问题在Django中运行python manage.py runserver时,可能会遇到以下错误:ImproperlyConfigured: Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-...
    编程 发布于2025-01-05
  • 在 Go 中使用 WebSocket 进行实时通信
    在 Go 中使用 WebSocket 进行实时通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要一种比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSoc...
    编程 发布于2025-01-05
  • 如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 识别今天生日的用户使用 MySQL 确定今天是否是用户的生日涉及查找生日匹配的所有行今天的日期。这可以通过一个简单的 MySQL 查询来实现,该查询将存储为 UNIX 时间戳的生日与今天的日期进行比较。以下 SQL 查询将获取今天有生日的所有用户: FROM USERS ...
    编程 发布于2025-01-05
  • 除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    无需强制转换即可上下文转换为 bool您的类定义了对 bool 的显式转换,使您能够在条件语句中直接使用其实例“t”。然而,这种显式转换提出了一个问题:“t”在哪里可以在不进行强制转换的情况下用作 bool?上下文转换场景C 标准指定了四种值可以根据上下文转换为的主要场景bool:语句:if、whi...
    编程 发布于2025-01-05
  • 大批
    大批
    方法是可以在对象上调用的 fns 数组是对象,因此它们在 JS 中也有方法。 slice(begin):将数组的一部分提取到新数组中,而不改变原始数组。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index p...
    编程 发布于2025-01-05
  • HTML 格式标签
    HTML 格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2025-01-05
  • 如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    在 PHP 中组合关联数组在 PHP 中,将两个关联数组组合成一个数组是一项常见任务。考虑以下请求:问题描述:提供的代码定义了两个关联数组,$array1 和 $array2。目标是创建一个新数组 $array3,它合并两个数组中的所有键值对。 此外,提供的数组具有唯一的 ID,而名称可能重合。要求...
    编程 发布于2025-01-05
  • 插入数据时如何修复“常规错误:2006 MySQL 服务器已消失”?
    插入数据时如何修复“常规错误:2006 MySQL 服务器已消失”?
    插入记录时如何解决“一般错误:2006 MySQL 服务器已消失”介绍:将数据插入 MySQL 数据库有时会导致错误“一般错误:2006 MySQL 服务器已消失”。当与服务器的连接丢失时会出现此错误,通常是由于 MySQL 配置中的两个变量之一所致。解决方案:解决此错误的关键是调整wait_tim...
    编程 发布于2025-01-05
  • 如何从 Pandas DataFrame 列中删除具有空值的行?
    如何从 Pandas DataFrame 列中删除具有空值的行?
    从 Pandas DataFrame 列中删除空值要根据特定列中的空值从 Pandas DataFrame 中删除行,请按照以下步骤操作步骤:1.识别列:确定 DataFrame 中包含要删除的空值的列。在本例中,它是“EPS”列。2。使用 dropna() 方法:dropna() 方法允许您根据特...
    编程 发布于2025-01-01
  • 如何在 Go 中正确键入断言接口值片段?
    如何在 Go 中正确键入断言接口值片段?
    类型断言接口值切片在编程中,经常会遇到需要类型断言接口值切片的情况。然而,这有时会导致错误。让我们深入研究一下为什么断言接口值切片可能并不总是可行的原因。当尝试从接口值切片中将断言键入特定类型(例如 []Symbol)时,[]Node ,如提供的示例中所示:args.([]Symbol)您可能会遇到...
    编程 发布于2025-01-01
  • 为什么 `list.sort()` 返回 `None` 以及如何获取排序列表?
    为什么 `list.sort()` 返回 `None` 以及如何获取排序列表?
    了解 Sort() 方法及其返回值尝试排序并返回唯一单词列表时,您可能会遇到常见问题:“return list.sort()”语法未按预期返回排序列表。这可能会令人困惑,因为它似乎与 sort() 方法的目的相矛盾。为了澄清这一点,让我们检查一下 list.sort() 的工作原理以及为什么它在这种...
    编程 发布于2025-01-01
  • 如何使“preg_match”正则表达式不区分大小写?
    如何使“preg_match”正则表达式不区分大小写?
    使 preg_match 不区分大小写在问题中提供的代码片段中,区分大小写导致无法实现预期结果。要纠正此问题,您可以在正则表达式中使用 i 修饰符,确保其不区分大小写。以下是修改代码的方法:preg_match("#(.{100}$keywords.{100})#i", stri...
    编程 发布于2025-01-01
  • DocumentFilter 如何有效地将 JTextField 输入限制为整数?
    DocumentFilter 如何有效地将 JTextField 输入限制为整数?
    将 JTextField 输入过滤为整数:使用 DocumentFilter 的有效方法虽然直观,但使用键侦听器来验证 JTextField 中的数字输入是不够的。相反,更全面的方法是使用 DocumentFilter。DocumentFilter:强大的解决方案DocumentFilter 监视文...
    编程 发布于2025-01-01

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3