详解TypeScript2.0标记联合类型

目录
  • 使用标记的联合类型构建付款方式
  • 使用标记联合类型构建 Redux 操作
  • never 类型
  • 永不返回的函数
  • 不可能有该类型的变量
  • never 和 void 之间的区别
  • 函数声明的类型推断

使用标记的联合类型构建付款方式

假设咱们为系统用户可以选择的以下支付方式建模

  • Cash (现金)
  • PayPal 与给定的电子邮件地址
  • Credit card 带有给定卡号和安全码

对于这些支付方法,咱们可以创建一个 TypeScript 接口

interface Cash {
  kind: "cash";
}

interface PayPal {
  kind: "paypal",
  email: string;
}

interface CreditCard {
  kind: "credit";
  cardNumber: string;
  securityCode: string;
}

注意,除了必需的信息外,每种类型都有一个kind属性,即所谓的判别属性。这里每种情况都是字符串字面量类型。

现在定义一个PaymentMethod类型,它是我们刚才定义的三种类型的并集。通过这种方式,用声明PaymentMethod每个变量, 必须具有给定的三种组成类型中的一种:

type PaymentMethod = Cash | PayPal | CreditCard;

现在我们的类型已经就绪,来编写一个函数来接受付款方法并返回一个读得懂的话语:

function describePaymentMethod(method: PaymentMethod) {
  switch (method.kind) {
    case "cash":
      // Here, method has type Cash
      return "Cash";

    case "paypal":
      // Here, method has type PayPal
      return `PayPal (${method.email})`;

    case "credit":
      // Here, method has type CreditCard
      return `Credit card (${method.cardNumber})`;
  }
}

首先,该函数包含的类型注释很少,method参数仅包含一个。除此之外,函数基本是纯 ES2015代码。

在switch语句的每个case中,TypeScript 编译器将联合类型缩小到它的一个成员类型。例如,当匹配到"paypal",method参数的类型从PaymentMethod缩小到PayPal。因此,咱们可以访问email属性,而不必添加类型断言。

本质上,编译器跟踪程序控制流以缩小标记联合类型。除了switch语句之外,它还要考虑条件以及赋值和返回的影响。

function describePaymentMethod(method: PaymentMethod) {
  if (method.kind === "cash") {
    // Here, method has type Cash
    return "Cash";
  }

  // Here, method has type PayPal | CreditCard

  if (method.kind === "paypal") {
    // Here, method has type PayPal
    return `PayPal (${method.email})`;
  }

  // Here, method has type CreditCard
  return `Credit card (${method.cardNumber})`;
}

控制流的类型分析使得使用标记联合类型非常顺利。使用最少的 TypeScript语法开销,咱可以编写几乎纯js,并且仍然可以从类型检查和代码完成中受益。

使用标记联合类型构建 Redux 操作

标记联合类型真正发挥作用的用例是在 TypeScript 应用程序中使用Redux时。 编写一个事例,其中包括一个模型,两个actions和一个Todo应用程序的reducer。

以下是一个简化的Todo类型,它表示单个todo。这里使用readonly修饰符为了防止属性被修改。

interface Todo {
  readonly text: string;
  readonly done: boolean;
}

用户可以添加新的 todos 并切换现有 todos 的完成状态。根据这些需求,咱们需要两个Redux操作,如下所示:

interface AddTodo {
  type: "ADD_TODO";
  text: string;
}

interface ToggleTodo {
  type: "TOGGLE_TODO";
  index: number
}

与前面的示例一样,现在可以将Redux操作构建为应用程序支持的所有操作的联合

type ReduxAction = AddTodo | ToggleTodo;

在本例中,type属性充当判别属性,并遵循Redux中常见的命名模式。现在添加一个与这两个action一起工作的Reducer:

function todosReducer(
  state: ReadonlyArray<Todo> = [],
  action: ReduxAction
): ReadonlyArray<Todo> {
  switch (action.type) {
    case "ADD_TODO":
      // action has type AddTodo here
      return [...state, { text: action.text, done: false }];

    case "TOGGLE_TODO":
      // action has type ToggleTodo here
      return state.map((todo, index) => {
        if (index !== action.index) {
          return todo;
        }

        return {
          text: todo.text,
          done: !todo.done
        };
      });

    default:
      return state;
  }
}

同样,只有函数签名包含类型注释。代码的其余部分是纯 ES2015,而不是特定于 TypeScript。

我们遵循与前面示例相同的逻辑。基于Redux操作的type属性,我们在不修改现有状态的情况下计算新状态。在switch语句的情况下,我们可以访问特定于每个操作类型的text和index属性,而不需要任何类型断言。

never 类型

TypeScript 2.0引入了一个新原始类型never。never类型表示值的类型从不出现。具体而言,never是永不返回函数的返回类型,也是变量在类型保护中永不为true的类型。

这些是never类型的确切特征,如下所述:

  • never是所有类型的子类型并且可以赋值给所有类型。
  • 没有类型是never的子类型或能赋值给never(never类型本身除外)。
  • 在函数表达式或箭头函数没有返回类型注解时,如果函数没有return语句,或者只有never类型表达式的return语句,并且如果函数是不可执行到终点的(例如通过控制流分析决定的),则推断函数的返回类型是never。
  • 在有明确never返回类型注解的函数中,所有return语句(如果有的话)必须有never类型的表达式并且函数的终点必须是不可执行的。

听得云里雾里的,接下来,用几个例子来讲讲never这位大哥。

永不返回的函数

下面是一个永不返回的函数示例:

// Type () => never
const sing = function() {
  while (true) {
    console.log("我就是不返回值,怎么滴!");
    console.log("我就是不返回值,怎么滴!");
    console.log("我就是不返回值,怎么滴!");
    console.log("我就是不返回值,怎么滴!");
    console.log("我就是不返回值,怎么滴!");
    console.log("我就是不返回值,怎么滴!");
  }
}

该函数由一个不包含break或return语句的无限循环组成,所以无法跳出循环。因此,推断函数的返回类型是never。

类似地,下面函数的返回类型被推断为never

// Type (message: string) => never
const failwith = (message: string) => {
  throw new Error(message);
};

TypeScript 推断出never类型,因为该函数既没有返回类型注释,也没有可到达的端点(由控制流分析决定)。

不可能有该类型的变量

另一种情况是,never类型被推断为从不为ture。在下面的示例中,我们检查value参数是否同时是字符串和数字,这是不可能的。

function impossibleTypeGuard(value: any) {
  if (
    typeof value === "string" &&
    typeof value === "number"
  ) {
    value; // Type never
  }
}

这个例子显然是过于作,来看一个更实际的用例。下面的示例展示了 TypeScript 的控制流分析缩小了类型守卫下变量的联合类型。直观地说,类型检查器知道,一旦咱们检查了value是字符串,它就不能是数字,反之亦然

function controlFlowAnalysisWithNever(
  value: string | number
) {
  if (typeof value === "string") {
    value; // Type string
  } else if (typeof value === "number") {
    value; // Type number
  } else {
    value; // Type never
  }
}

注意,在最后一个else分支中,value既不能是字符串,也不能是数字。在这种情况下,TypeScript 推断出never类型,因为咱们已经将value参数注解为类型为string | number,也就是说,除了string或number,value参数不可能有其他类型。

一旦控制流分析排除了string和number作为value类型的候选项,类型检查器就推断出never类型,这是惟一剩下的可能性。但是,咱们也就不能对value做任何有用的事情,因为它的类型是never,所以咱们的编辑器工具不会显示自动显示提示该值有哪些方法或者属性可用。

never 和 void 之间的区别

你可能会问,为什么 TypeScript 已经有一个void类型为啥还需要never类型。虽然这两者看起来很相似,但它们是两个不同的概念:

没有显式返回值的函数将隐式返回undefined。虽然我们通常会说这样的函数“不返回任何东西”,但它会返回。在这些情况下,我们通常忽略返回值。这样的函数在 TypeScript 中被推断为有一个void返回类型。

具有never返回类型的函数永不返回。它也不返回undefined。该函数没有正常的完成,这意味着它会抛出一个错误,或者根本不会完成运行。

函数声明的类型推断

关于函数声明的返回类型推断有一个小问题。咱们前面列出的几条never特征,你会发现下面这句话:

在函数表达式或箭头函数没有返回类型注解时,如果函数没有return语句,或者只有never类型表达式的return语句,并且如果函数是不可执行到终点的(例如通过控制流分析决定的),则推断函数的返回类型是never。

它提到了函数表达式和箭头函数,但没有提到函数声明。也就是说,为函数表达式推断的返回类型可能与为函数声明推断的返回类型不同:

// Return type: void
function failwith1(message: string) {
  throw new Error(message);
}

// Return type: never
const failwith2 = function(message: string) {
  throw new Error(message);
};

这种行为的原因是向后兼容性,如下所述。如果希望函数声明的返回类型never,则可以对其进行显式注释:

function failwith1(message: string): never {
  throw new Error(message);
}

以上就是详解TypeScript2.0标记联合类型的详细内容,更多关于TS2.0标记联合类型的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解TypeScript中的类型保护

    概述 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird { // 独有方法 fly(); // 共有方法 layEggs(); } interface Fish { // 独有方法 swim(); // 共有方法 layEggs(); } function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet(); pet.layEggs(); // 正常 pet.swim();

  • 如何通俗的解释TypeScript 泛型

    概述 在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束.这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型.那么,究竟什么是泛型?如果通俗的理解泛型呢? 什么是泛型 泛型(Generics)是指在定义函数.接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性. 通俗的解释,泛型是类型系统中的"参数",主要作用是为了类型的重用.从上面定义可以看出,它只会用在函数.接口和类中.它和js程序中的函数参数是两个层面的事物

  • 7个好用的TypeScript新功能

    1. 可选链 从 v3.7 可用 这是当你尝试访问嵌套数据时的一个痛点,嵌套数据越多,代码就会变得越繁琐. 在下面的例子中,要访问address,你必须遍历data.customer.address,而且data或customer有可能是undefined,所以通常使用&&运算符或类似例子中的技巧遍历检查每个层次的定义. 现在你可以用.?运算符来选择性地对数据访问.通过这种方式,如果存在尚未定义的父级对象,则会在链中的任何位置返回未定义,而不是在运行时崩溃. // v3.7 以前 if (

  • TypeScript魔法堂之枚举的超实用手册

    前言 也许前端的同学会问JavaScript从诞生至今都没有枚举类型,我们不是都活得挺好的吗?为什么TypeScript需要引入枚举类型呢? 也许被迫写前端的后端同学会问,TypeScript的枚举类型是和Java/.NET的一样吗? 下面我们来一起探讨和尝试解答吧! 前端一直都需要枚举 我敢保证,前端的同学都会万分肯定地告诉大家:我们从来没有写过枚举.那是因为虽然ECMAScript将enum作为保留字,但至ES2020为止还没有提出枚举的实现规范.语言没有提供规范和语言实现,不代表思想活跃勇

  • typescript配置alias的详细步骤

    1 安装依赖 npm install --save-dev babel-plugin-module-resolver # yarn add babel-plugin-module-resolver --dev 根目录新增.babelrc文件 参考以下内容按您项目中的需要去修改 { "presets": ["next/babel"], "plugins": [ [ "module-resolver", { "alias

  • TypeScript泛型参数默认类型和新的strict编译选项

    概述 TypeScript 2.3 增加了对声明泛型参数默认类型的支持,允许为泛型类型中的类型参数指定默认类型. 接下来看看如何通过泛型参数默认将以下react组件从js(和jsX)迁移到 TypeScript (和TSX): class Greeting extends react.Component { render() { return <span>Hello, {this.props.name}!</span>; } } 为组件类创建类型定义 咱们先从为Component类

  • TypeScript在React项目中的使用实践总结

    序言 本文会侧重于TypeScript(以下简称TS)在项目中与React的结合使用情况,而非TS的基本概念.关于TS的类型查看可以使用在线TS工具

  • 浅析TypeScript 命名空间

    TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准. TypeScript 由微软开发的自由和开源的编程语言. TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上. 命名空间一个最明确的目的就是解决重名问题. 假设这样一种情况,当一个班上有两个名叫小明的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的姓(王小明,李小明),或者他

  • 你不知道的 TypeScript 高级类型(小结)

    前言 对于有 JavaScript 基础的同学来说,入门 TypeScript 其实很容易,只需要简单掌握其基础的类型系统就可以逐步将 JS 应用过渡到 TS 应用. // js const double = (num) => 2 * num // ts const double = (num: number): number => 2 * num 然而,当应用越来越复杂,我们很容易把一些变量设置为 any 类型,TypeScript 写着写着也就成了 AnyScript.为了让大家能更加深入

  • 详解TypeScript2.0标记联合类型

    目录 使用标记的联合类型构建付款方式 使用标记联合类型构建 Redux 操作 never 类型 永不返回的函数 不可能有该类型的变量 never 和 void 之间的区别 函数声明的类型推断 使用标记的联合类型构建付款方式 假设咱们为系统用户可以选择的以下支付方式建模 Cash (现金) PayPal 与给定的电子邮件地址 Credit card 带有给定卡号和安全码 对于这些支付方法,咱们可以创建一个 TypeScript 接口 interface Cash { kind: "cash&quo

  • 详解vue3.0 diff算法的使用(超详细)

    前言:随之vue3.0beta版本的发布,vue3.0正式版本相信不久就会与我们相遇.尤玉溪在直播中也说了vue3.0的新特性typescript强烈支持,proxy响应式原理,重新虚拟dom,优化diff算法性能提升等等.小编在这里仔细研究了vue3.0beta版本diff算法的源码,并希望把其中的细节和奥妙和大家一起分享. 首先我们来思考一些大中厂面试中,很容易问到的问题: 1 什么时候用到diff算法,diff算法作用域在哪里? 2 diff算法是怎么运作的,到底有什么作用? 3 在v-f

  • 详解Redis中的List类型

    本系列将和大家分享Redis分布式缓存,本章主要简单介绍下Redis中的List类型,以及如何使用Redis解决博客数据分页.生产者消费者模型和发布订阅等问题. Redis List的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用这个数据结构. List类型主要用于队列和栈,先进先出,后进先出等. 存储形式:key--LinkList<value> 首先先给大家Show一波Redis中与List类型相

  • 详解javaScript中Number数字类型的使用

    目录 前言 Number数字 自带属性值 基础使用 总结 源码地址 前言 Number和Math都属于JavaScript中的内置对象,Number数字类型作为基础数据类型,我们在开发过程中会经常用到,包括数字精度的格式化,还有字符串转换成数字等操作. Number数字 自带属性值 Number.EPSILON 两个可表示(representable)数之间的最小间隔. Number.MAX_SAFE_INTEGER JavaScript 中最大的安全整数 (2^53 - 1). Number.

  • 详解Python中的枚举类型

    目录 什么是枚举类型 为什么要使用枚举 如何使用枚举 从字典创建枚举 最后的话 你好,我是 征哥,今天分享一下 Python 中的枚举类型,为什么需要枚举类型,及如何使用. 什么是枚举类型 枚举(Enum)是一种数据类型,是绑定到唯一值的符号表示.您可以使用它来创建用于变量和属性的常量集.它们类似于全局变量,但是,它们提供了更有用的功能,例如分组和类型安全.Python 在 3.4 版本中添加了标准库 enum. 为什么要使用枚举 使用枚举有以下好处: 代码更容易阅读,更容易维护. 减少由转换或

  • 详解 swift3.0 可选绑定共用同一块内存空间的实例

    详解 swift3.0 可选绑定共用同一块内存空间的实例 示例代码: ljTempModel = UserModel.init(userName: "sww", userID: 12, phone: "123", email: "deew") ljTempModel?.ljArray.append("sww") print("可选绑定前:\(ljTempModel?.ljArray)") //可选绑定成功,

  • 详解MySQL8.0​ 字典表增强

    MySQL中数据字典是数据库重要的组成部分之一,INFORMATION_SCHEMA首次引入于MySQL 5.0,作为一种从正在运行的MySQL服务器检索元数据的标准兼容方式.用于存储数据元数据.统计信息.以及有关MySQL server的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等). 8.0之前: 1.元数据来自文件 2.采用MEMORY表引擎 3.frm文件 存放表结构信息 4.opt文件,记录了每个库的一些基本信息,包括库的字符集等信息 5..TRN,.TRG文件用于存放触

  • 详解Mybatis-plus中更新date类型数据遇到的坑

    最近一年的项目都是在使用Mybatis-plus,感觉挺好用的,也没遇到很多问题,但是在最近项目上线之后,遇到了一些新的需要,在进行新版本开发的时候就开始遇到坑了,今天来说一下更新数据中有date类型数据的时候会出现的问题. 实体类部分字段如下: @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ProductPo implements Serializable { /** * 产品主键,自增 */ privat

  • 详解MySQL8.0 密码过期策略

    MySQL8.0.16开始,可以设置密码的过期策略,今天针对这个小的知识点进行展开. 1.手工设置单个密码过期 MySQL8.0中,我们可以使用alter user这个命令来让密码过期. 首先我们创建账号yeyz,密码是yeyz [root@VM-0-14-centos ~]# /usr/local/mysql-8.0.19-el7-x86_64/bin/mysql -uyeyz -pyeyz -h127.0.0.1 -P4306 -e "select 1" mysql: [Warni

  • 详解Vue3.0 + TypeScript + Vite初体验

    项目创建 npm: $ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev or yarn: $ yarn create vite-app <project-name> $ cd <project-name> $ yarn $ yarn dev 项目结构 main.js 在个人想法上,我觉得createApp()是vue应用的实例,createApp

随机推荐