前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >组合类型与类型保护_TypeScript笔记9

组合类型与类型保护_TypeScript笔记9

作者头像
ayqy贾杰
发布2019-06-12 15:10:45
1.5K0
发布2019-06-12 15:10:45
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

一.组合类型

交叉类型(intersection types)

组合多个类型产生新类型,源类型间存在“与”关系,例如:

代码语言:javascript
复制
interface ObjectConstructor {
  assign<T, U>(target: T, source: U): T & U;
}

(摘自TypeScript/lib/lib.es2015.core.d.ts)

Object.assign能把source: U身上的可枚举属性浅拷贝到target: T上,因此返回值类型为T & U

交叉类型A & B既是A也是B,因此具有各个源类型的所有成员:

代码语言:javascript
复制
interface A {
  a: string;
}
interface B {
  b: number
}

let x: A & B;
// 都是合法的
x.a;
x.b;

P.S.虽然名字叫intersection(交集),实际上是“求并集”

联合类型(union types)

类似于交叉类型,联合类型由具有“或”关系的多个类型组合而成,例如:

代码语言:javascript
复制
interface DateConstructor {
  new (value: number | string | Date): Date;
}

(摘自TypeScript/lib/lib.es2015.core.d.ts)

Date构造函数接受一个numberstringDate类型的参数,对应类型为number | string | Date

联合类型A | B要么是A要么是B,因此只有所有源类型的公共成员(“交集”)才能访问:

代码语言:javascript
复制
interface A {
  id: 'a';
  a: string;
}
interface B {
  id: 'b';
  b: number
}

let x: A | B;
x.id;
// 错误 Property 'a' does not exist on type 'A | B'.
x.a;
// 错误 Property 'b' does not exist on type 'A | B'.
x.b;

二.类型保护

联合类型相当于由类型构成的枚举类型,因而无法确定其具体类型:

联合类型A | B要么是A要么是B

这在函数签名上没什么问题,但在函数实现中,通常需要区分出具体类型,例如:

代码语言:javascript
复制
let createDate: (value: number | string | Date) => Date;
createDate = function(value) {
  let date: Date;
  if (typeof value === 'string') {
    value = value.replace(/-/g, '/');
    // ...
  }
  else if (typeof value === 'number') {/*...*/}
  else if (value instanceof Date) {/*...*/}

  return date;
};

因此,在此类场景下,需要把“宽”的联合类型,“缩窄”到一个具体类型。从类型角度来看,上面代码在理想情况下应该是这样的:

代码语言:javascript
复制
function(value) {
  // 此处,value是联合类型,要么number要么string要么Date

  if (typeof value === 'string') {
    // 此分支下,value是string
  }
  else if (typeof value === 'number') {
    // 此分支下,value是number
  }
  else if (value instanceof Date) {
    // 此分支下,value是Date
  }

  // 此处,value是联合类型,要么number要么string要么Date
}

也就是说,需要有一种机制能让我们告诉类型系统,“听着,现在我知道这个东西的具体类型了,请把它圈小一些”

而这种机制,就是类型保护(type guard)

A type guard is some expression that performs a runtime check that guarantees the type in some scope.

typeof类型保护

typeof variable === 'type'是用来确定基本类型的惯用手法,因此TypeScript能够识别typeof,并自动缩窄对应分支下的联合类型:

代码语言:javascript
复制
let x: number | string;
if (typeof x === 'string') {
  // 正确 typeof类型保护,自动缩窄到string
  x.toUpperCase();
}

switch语句,&&等其它分支结构中也同样适用:

代码语言:javascript
复制
switch (typeof x) {
  case 'number':
    // 正确 typeof类型保护
    x.toFixed();
    break;
}
// 正确 typeof类型保护
typeof x !== 'number' && x.startsWith('xxx');

注意,最后一例很有意思,x要么是number要么是string,从typeof判断得知不是number,因此缩窄到string

具体的,typeof类型保护能够识别两种形式的typeof

  • typeof v === "typename"
  • typeof v !== "typename"

并且typename只能是numberstringbooleansymbol,因为其余的typeof检测结果不那么可靠(具体见typeof),所以不作为类型保护,例如:

代码语言:javascript
复制
let x: any;
if (typeof x === 'function') {
  // any类型,typeof类型保护不适用
  x;
}
if (typeof x === 'object') {
  // any类型,typeof类型保护不适用
  x;
}

P.S.相关讨论,见typeof a === “object” does not type the object as Object

instanceof类型保护

类似于typeof检测基本类型,instanceof用来检测实例与“类”的所属关系,也是一种类型保护,例如:

代码语言:javascript
复制
let x: Date | RegExp;
if (x instanceof RegExp) {
  // 正确 instanceof类型保护,自动缩窄到RegExp实例类型
  x.test('');
}
else {
  // 正确 自动缩窄到Date实例类型
  x.getTime();
}

具体的,要求instanceof右侧是个构造函数,此时左侧类型会被缩窄到:

  • 该类实例的类型(构造函数prototype属性的类型)
  • (构造函数存在重载版本时)由构造函数返回类型构成的联合类型

例如:

代码语言:javascript
复制
// Case1 该类实例的类型
let x;
if (x instanceof Date) {
  // x从any缩窄到Date
  x.getTime();
}

// Case2 由构造函数返回类型构成的联合类型
interface DateOrRegExp { 
  new(): Date;
  new(value?: string): RegExp;
}

let A: DateOrRegExp;
let y;
if (y instanceof A) {
  // y从any缩窄到RegExp | Date
  y;
}

P.S.关于instanceof类型保护的更多信息,见4.24 Type Guards

P.S.另外,class具有双重类型含义,在TypeScript代码里的体现形式如下:

  • 类的类型:typeof className
  • 类实例的类型:typeof className.prototype或者className

例如:

代码语言:javascript
复制
class A {
  static prop = 'prop';
  id: 'b'
}

// 类的类型
let x: typeof A;
x.prop;
// 错误 id是实例属性,类上不存在
x.id;

// 类实例的类型
let y: typeof A.prototype;
let z: A;
// 二者类型等价
z = y;
// 错误 prop是静态属性,实例上不存在
z.prop;
z.id;

也就是说,类实例的类型等价于构造函数prototype属性的类型。但这仅在TypeScript的编译时成立,与JavaScript运行时概念有冲突:

代码语言:javascript
复制
class A {}
class B extends A {}
// 构造函数prototype属性是父类实例,其类型是父类实例的类型
B.prototype instanceof A === true

自定义类型保护

typeofinstanceof类型保护能够满足一般场景,对于一些更加特殊的,可以通过自定义类型保护来缩窄类型:

代码语言:javascript
复制
interface RequestOptions {
  url: string;
  onSuccess?: () => void;
  onFailure?: () => void;
}

// 自定义类型保护,将参数类型any缩窄到RequestOptions
function isValidRequestOptions(opts: any): opts is RequestOptions {
  return opts && opts.url;
}

let opts;
if (isValidRequestOptions(opts)) {
  // opts从any缩窄到RequestOptions
  opts.url;
}

自定类型保护与普通函数声明类似,只是返回类型部分是个类型谓词(type predicate)

代码语言:javascript
复制
parameterName is Type

其中parameterName必须是当前函数签名中的参数名,例如上面的opts is RequestOptions

调用带类型谓词的函数后,传入参数的类型会被缩窄到指定类型,与前两种类型保护行为一致:

代码语言:javascript
复制
let isNumber: (value: any) => value is number;

let x: string | number;
if (isNumber(x)) {
  // 缩窄到number
  x.toFixed(2);
}
else {
  // 不是number就是string
  x.toUpperCase();
}

三.Nullable与联合类型

TypeScript里空类型(Void)有两种:Undefined与Null,是(除Never外)其它所有类型的子类型。因此nullundefined可以赋值给其它任何类型:

代码语言:javascript
复制
let x: string;
x = null;
x = undefined;
// 运行时错误,编译时不报错
x.toUpperCase();

从类型上看,Nullable类型相当于原类型与null | undefined组成的联合类型(上例中,相当于let x: string | null | undefined;

这意味着类型检查不那么十分可靠,因为仍无法避免undefined/null.xxx之类的错误

strictNullChecks

针对空类型的潜在问题,TypeScript提供了--strictNullChecks选项,开启之后会严格检查空类型:

代码语言:javascript
复制
let x: string;
// 错误 Type 'null' is not assignable to type 'string'.
x = null;
// 错误 Type 'undefined' is not assignable to type 'string'.
x = undefined;

对于可以为空的类型,需要显式声明:

代码语言:javascript
复制
let y: string | undefined;
y = undefined;
// Type 'null' is not assignable to type 'string | undefined'.
y = null;

同时,可选参数和可选属性会自动带上| undefined,例如:

代码语言:javascript
复制
function createDate(value?: string) {
  // 错误 Object is possibly 'undefined'.
  value.toUpperCase();
}

interface Animal {
  color: string;
  name?: string;
}
let x: Animal;
// 错误 Type 'undefined' is not assignable to type 'string'.
x.color = undefined;
// 错误 Object is possibly 'undefined'.
x.name.toUpperCase();

类似的空值相关问题都能够暴露出来,由此看来,空类型严格检查相当于一种编译时检查追溯空值的能力

!后缀类型断言

既然Nullable类型实质上是联合类型,那么同样面临类型缩窄的问题。对此,TypeScript也提供了符合直觉的类型保护:

代码语言:javascript
复制
function createDate(value: string | undefined) {
  // 缩窄到string
  value = value || 'today';
  value.toUpperCase();
}

对于自动类型保护无法处理的场景,可以简单地通过!后缀去掉| undefined | null成分:

代码语言:javascript
复制
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    // 通过!去掉类型中的null成分,使之缩窄到string
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}

identifier!相当于类型断言(不同于类型保护):

代码语言:javascript
复制
let x: string | undefined | null;
x!.toUpperCase();
// 相当于
(<string>x).toUpperCase();
// 或者
(x as string).toUpperCase();
// Object is possibly 'null' or 'undefined'.
x.toUpperCase();

P.S.类型断言与类型保护的区别在于,断言是一次性的(或者说是临时的),而类型保护在一定作用域下都有效

参考资料

  • Advanced Types
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.组合类型
    • 交叉类型(intersection types)
      • 联合类型(union types)
      • 二.类型保护
        • typeof类型保护
          • instanceof类型保护
            • 自定义类型保护
            • 三.Nullable与联合类型
              • strictNullChecks
                • !后缀类型断言
                  • 参考资料
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档