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

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

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

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]删除
最新教程 更多>
  • 网站 HTML 代码
    网站 HTML 代码
    我一直在尝试建立一个与航空公司相关的网站。我只是想确认我是否可以使用人工智能生成代码来生成整个网站。 HTML 网站是否兼容博客,或者我应该使用 JavaScript?这是我用作演示的代码。 <!DOCTYPE html> <html lang="en">[](url) &l...
    编程 发布于2024-11-05
  • 像程序员一样思考:学习 Java 基础知识
    像程序员一样思考:学习 Java 基础知识
    本文介绍了 Java 编程的基本概念和结构。它首先介绍了变量和数据类型,然后讨论了操作符和表达式,以及控制流流程。其次,它解释了方法和类,然后介绍了输入和输出操作。最后,本文通过一个工资计算器的实际示例展示了这些概念的应用。像程序员一样思考:掌握 Java 基础1. 变量和数据类型Java 使用变量...
    编程 发布于2024-11-05
  • PHP GD 可以比较两个图像的相似性吗?
    PHP GD 可以比较两个图像的相似性吗?
    PHP GD 可以确定两个图像的相似度吗?正在考虑的问题询问是否可以使用以下命令确定两个图像是否相同PHP GD 通过比较它们的差异。这需要获取两个图像之间的差异并确定它是否完全由白色(或任何统一的颜色)组成。根据提供的答案,散列函数(如其他响应所建议的那样)不适用于此语境。比较必须涉及图像内容而不...
    编程 发布于2024-11-05
  • 使用这些键编写高级测试(JavaScript 中的测试需求)
    使用这些键编写高级测试(JavaScript 中的测试需求)
    在本文中,您将学习每个高级开发人员都应该了解的 12 个测试最佳实践。您将看到 Kent Beck 的文章“Test Desiderata”的真实 JavaScript 示例,因为他的文章是用 Ruby 编写的。 这些属性旨在帮助您编写更好的测试。了解它们还可以帮助您在下一次工作面试中取得好成绩。 ...
    编程 发布于2024-11-05
  • 通过将 matlab/octave 算法移植到 C 来实现 AEC 的最佳解决方案
    通过将 matlab/octave 算法移植到 C 来实现 AEC 的最佳解决方案
    完毕!对自己有点印象。 我们的产品需要回声消除功能,确定了三种可能的技术方案, 1)利用MCU检测audio out和audio in的音频信号,编写算法计算两侧声音信号的强度,根据audio out和audio in的强弱在两个通道之间进行可选的切换,实现半双工通话效果,但现在市场上都是全双工通话...
    编程 发布于2024-11-05
  • 逐步构建网页:探索 HTML 中的结构和元素
    逐步构建网页:探索 HTML 中的结构和元素
    ?今天标志着我软件开发之旅的关键一步! ?我编写了第一行代码,深入研究了 HTML 的本质。涵盖的元素和标签。昨天,我探索了构建网站的拳击技术,今天我通过创建页眉、页脚和内容区域等部分将其付诸实践。我还添加了各种 HTML 元素,包括图像元素和链接元素,甚至尝试在单页网站上进行内部链接。看到这些部分...
    编程 发布于2024-11-05
  • 项目创意不一定是独特的:原因如下
    项目创意不一定是独特的:原因如下
    在创新领域,存在一个常见的误解,即项目创意需要具有开创性或完全独特才有价值。然而,事实并非如此。我们今天使用的许多成功产品与其竞争对手共享一组核心功能。让他们与众不同的不一定是想法,而是他们如何执行它、适应用户需求以及在关键领域进行创新。 通讯应用案例:相似但不同 让我们考虑一下 M...
    编程 发布于2024-11-05
  • HackTheBox - Writeup 社论 [已退休]
    HackTheBox - Writeup 社论 [已退休]
    Neste writeup iremos explorar uma máquina easy linux chamada Editorial. Esta máquina explora as seguintes vulnerabilidades e técnicas de exploração: S...
    编程 发布于2024-11-05
  • 强大的 JavaScript 技术可提升您的编码技能
    强大的 JavaScript 技术可提升您的编码技能
    JavaScript is constantly evolving, and mastering the language is key to writing cleaner and more efficient code. ?✨ Whether you’re just getting starte...
    编程 发布于2024-11-05
  • 如何在 ReactJS 中创建可重用的 Button 组件
    如何在 ReactJS 中创建可重用的 Button 组件
    按钮无疑是任何 React 应用程序中重要的 UI 组件,按钮可能用于提交表单或打开新页面等场景。您可以在 React.js 中构建可重用的按钮组件,您可以在应用程序的不同部分中使用它们。因此,维护您的应用程序将变得更加简单,并且您的代码将保持 DRY(不要重复自己)。 您必须首先在组件文件夹中创建...
    编程 发布于2024-11-05
  • 如何在 Apache HttpClient 4 中实现抢占式基本身份验证?
    如何在 Apache HttpClient 4 中实现抢占式基本身份验证?
    使用 Apache HttpClient 4 简化抢占式基本身份验证虽然 Apache HttpClient 4 已经取代了早期版本中的抢占式身份验证方法,但它提供了替代方法以实现相同的功能。对于寻求直接抢占式基本身份验证方法的开发人员,本文探讨了一种简化方法。为了避免向每个请求手动添加 Basic...
    编程 发布于2024-11-05
  • 异常处理
    异常处理
    异常是运行时发生的错误。 Java 中的异常处理子系统允许您以结构化和受控的方式处理错误。 Java为异常处理提供了易于使用且灵活的支持。 主要优点是错误处理代码的自动化,以前必须手动完成。 在旧语言中,需要手动检查方法返回的错误码,既繁琐又容易出错。 异常处理通过在发生错误时自动执行代码块(异常...
    编程 发布于2024-11-05
  • 如何在不使用“dangerouslySetInnerHTML”的情况下安全地在 React 中渲染原始 HTML?
    如何在不使用“dangerouslySetInnerHTML”的情况下安全地在 React 中渲染原始 HTML?
    使用更安全的方法在 React 中渲染原始 HTML在 React 中,您现在可以使用更安全的方法渲染原始 HTML,避免使用危险的SetInnerHTML 。这里有四个选项:1。 Unicode 编码使用 Unicode 字符表示 UTF-8 编码文件中的 HTML 实体:<div>{...
    编程 发布于2024-11-05
  • PHP 死了吗?不,它正在蓬勃发展
    PHP 死了吗?不,它正在蓬勃发展
    PHP 是一种不断受到批评但仍在蓬勃发展的编程语言。 使用率:根据 W3Techs 的数据,截至 2024 年 8 月,全球 75.9% 的网站仍在使用 PHP,其中 43% 的网站基于 WordPress。使用PHP作为开发语言的主流网站中,超过70%包括Facebook、微软、维基百科、Mozi...
    编程 发布于2024-11-05
  • PgQueuer:将 PostgreSQL 转变为强大的作业队列
    PgQueuer:将 PostgreSQL 转变为强大的作业队列
    PgQueuer 简介:使用 PostgreSQL 实现高效作业队列 社区开发者您好! 我很高兴分享一个项目,我相信该项目可以显着简化开发人员在使用 PostgreSQL 数据库时处理作业队列的方式。 PgQueuer,这是一个 Python 库,旨在利用 PostgreSQL 的...
    编程 发布于2024-11-05

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

Copyright© 2022 湘ICP备2022001581号-3