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

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

  • 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标记联合类型的资料请关注Devmax其它相关文章!

详解TypeScript2.0标记联合类型的更多相关文章

  1. Swift Selector

    看了喵神的Swifter100个必备的小tips总结下swift中不支持@selector关键字,将SEL重新定义为结构体了。

  2. 使用sockets:从新闻组中获取文章(三)

    >我们从服务器的这个新闻组中读取了最后的十篇文章,。也可以通过使用HEAD命令读取文章的头信息,或者使用BODY命令读取文章内容。>关于fclose()的更多信息,请参考http://www.php.net/manual/function.fclose.php结论在上文中,我们看到了怎样打开、使用然后关闭一个socket:连接到一个NNTP服务器,取回一些文章。使用POST命令发表文章也复杂不到哪儿去。下一步就是编写一个基于WEB的新闻组客户端了。这样,你有了一个基于web的搜索新闻组的程序了。

  3. Typescript中extends关键字的基本使用

    extends表示具体的泛型类型只能是object类型,某个变量如果能断言成object类型[变量as object],那么这个变量的类型符合T extends object,下面这篇文章主要给大家介绍了关于Typescript中extends关键字基本使用的相关资料,需要的朋友可以参考下

  4. JDBCTM 指南:入门5 - ResultSet

    JavaSoft目前正在准备这本书。ResultSet.next方法用于移动到ResultSet中的下一行,使下一行成为当前行。在ResultSet对象或其父辈Statement对象关闭之前,光标一直保持有效。关于ResultSet中列的信息,可通过调用方法ResultSet.getMetaData得到。返回的ResultSetMetaData对象将给出其ResultSet对象各列的编号、类型和属性。getString的返回值将为JavaString对象。TIYITSMALLITITEGERBIGNTRE

  5. TypeScript中type和interface的区别及注意事项

    type的类型别用可以用户其他的类型,比如联合类型、元祖类型、基本类型,interface不行,下面这篇文章主要给大家介绍了关于TypeScript中type和interface的区别及注意事项的相关资料,需要的朋友可以参考下

  6. 一步步带你用vite简单搭建ts+vue3全家桶

    Vue3与TS的联合是大趋势,下面这篇文章主要给大家介绍了关于用vite简单搭建ts+vue3全家桶的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

  7. vite创建一个标准vue3+ts+pinia项目

    本文主要介绍了vite创建一个标准vue3+ts+pinia项目,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  8. Vue3+TS实现语音播放组件的示例代码

    这篇文章主要介绍了如何利用Vue+TS实现一个简易的语音播放组件,文中的示例代码讲解详细,对我们学习Vue有一定的帮助,需要的可以参考一下

  9. vue3 ts编写echart是tooltip无法展示的解决

    这篇文章主要介绍了vue3 ts编写echart是tooltip无法展示的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  10. 使用sockets:从新闻组中获取文章(一)

    本文是一个使用Socket的小例子:连接到一个Usenet新闻组服务器,同服务器对话,从新闻组中下载一些文章。函数声明是这样的:intfsockopen这个函数将打开一个连接到主机hostname的port端口的TCP连接。对于udp连接,你必须指定协议:udp://hostname.对于unix域,主机名使用到socket的路径,这种情况下,端口port必须置为0。可选的timeout参数用来设定等待打开一个socket的时间,单位为秒。关于fsockopen()的更多信息,请参考:http://www

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部