TypeScript 中的类型收窄

在本文中,我们将学习各种收窄类型的方法。类型(narrowing)收窄是将类型从不太精确的类型推导为更精确的类型的过程。

让我们从一个简单的函数开始:

function friends(input: string | number) {
  // code here
}

上面的函数可以接受一个数字或一个字符串。假设我们要根据 input 是数字还是字符串来执行不同的操作。在这种情况下,我们将使用 JavaScript 类型保护来检查它是字符串还是数字,如下所示:

function someFunc(input: string | number) {
  if (typeof input === 'string') {
    // do something with the string
    console.log('input is a string')
  }

  if (typeof input === 'number') {
    // do something with number
    console.log('input is a number')
  }
}

类型保护 — Type Guard

在上面的示例中,我们使用 JavaScript 类型保护将输入类型收窄为数字或字符串。类型保护用于检查变量是否属于特定类型,即数字、字符串、对象等。使用类型保护时,Typescript 希望该变量属于该类型。它将根据该信息自动键入并检查其使用情况。

示例中,我们使用 typeof 运算符收窄类型,它检查值是否具有原始类型的类型。

以下是可用的 JavaScript 类型保护的列表:

string

if (typeof param === 'string') {
  // do something with string value
}

number

if (typeof param === 'number') {
  // do something with number value
}

bigint

if (typeof param === 'bigint') {
  // do something with bigint value
}

boolean

if (typeof param === 'boolean') {
  // do something with boolean value
}

symbol

if (typeof param === 'symbol') {
  // do something with symbol value
}

undefined

if (typeof param === 'undefined') {
  // do something with undefined value
}

object

if (typeof param === 'object') {
  // do something with object value
}

function

if (typeof param === 'function') {
  // do something with the function
}

下面我们来看一下其他的收窄类型方法。

真值收窄

在这种类型收窄中,我们在使用变量之前检查它是否为真。当变量为真时,TypeScript 将自动消除该变量在条件检查中出错的可能性,即 undefinednull 等。

举个例子,下面的函数 foo 接受一个参数 x,其类型为 stringundefined(即可选)。

function foo(x?: string) {
  if (x) {
    console.log(typeof x) // "string"
  }
}

foo('TS')

通过检查 x 是否为 truex 的类型将变为 string,否则为 undefined

很多情况下都会进行真值收窄:表达式(||&&!)、!!Boolean 等。

等值收窄

如果两个变量相等,那么两个变量的类型必须相同。如果一个变量的类型不精确(即 unknownany 等),并且等于另一个精确类型的变量,那么 TypeScript 将使用该信息收窄第一个变量的类型。

以下面的函数为例,它有两个参数:xyxstringnumberynumber。当 x 的值等于 y 的值时,x 的类型被推断为 number,否则为 string

function someFunction(x: string | number, y: number) {
  if (x === y) {
    // 收窄到 number
    console.log(typeof x) // number
  } else {
    // 这并没有收窄范围
    console.log(typeof x) // number or string
  }
}

可识别联合

在这种方法中,您创建了一个对象,其中包含一个文本成员,可以用来区分两个不同的联合。

以下例子中,函数计算不同形状的正方形、矩形和圆形。我们将从定义 RectangleCircle 的类型开始。

type Rectangle = {
  shape: 'reactangle'
  width: number
  height: number
}

type Circle = {
  shape: 'circle'
  radius: number
}

从上述类型中,每个对象都将具有 shape 的文字字段,可以是 circlerectangle。我们可以使用函数中的 shape 字段来计算面积,这将接受 RectangleCircle 的并集,如下所示:

function calculateArea(shape: Rectangle | Circle) {
  if (shape.shape === 'reactangle') {
    // 只能访问 reactangle 的属性,不能访问 circle 的属性
    console.log(shape.height * shape.width)
  }

  if (shape.shape === 'circle') {
    // 只能访问 circle 的属性,而不能访问 reactangle 的属性
    console.log(3.14 * shape.radius * shape.radius)
  }
}

shape 字段是矩形时,您只能访问 Rectangle 类型中可用的属性,即 widthheightshape。这同样适用于当 shape 字段是 circle 时,TypesSript 只允许您访问 radiuscircle,否则将抛出错误。

使用 in 运算符进行收窄

in 运算符用于确定对象是否具有包含的属性名。它以 "property" in object 的格式使用,其中 property 是要检查其是否存在于 object 中的属性名。

在上面的例子中,我们使用有区别的并集来区分圆和矩形。我们也可以使用 in 运算符来实现同样的效果,但这次我们将检查一个形状是否包含某些属性,即 CircleradiusRectanglewidthheight,结果将是相同的。

type Circle = {
  radius: number
}

type Reactangle = {
  width: number
  height: number
}

function calculateArea(shape: Circle | Reactangle) {
  if ('radius' in shape) {
    // 现在,您可以从 shape 访问 radius
    console.log(3.14 * shape.radius * shape.radius)

    //  任何访问 height 或 width 的尝试都将导致错误
    shape.width // Error
    shape.height // Error
  }
  if ('width' in shape && 'height' in shape) {
    // 现在可以从 shape 对象访问 height 和 width 
    console.log(shape.height * shape.width)

    // 任何访问 raidus 的尝试都会导致错误
    shape.radius // TypeError
  }
}

使用赋值收窄

在这种类型的收窄中,一旦给变量赋值,TypeScript 就会收窄变量的类型。取一个 numberstring 的联合类型的变量 x,如果我们给它赋值 number,类型就变成了一个数,如果我们给它赋值 string,类型就变成了字符串。

let x: number | string = 1
console.log(typeof x) // "number"

x = 'something'
console.log(typeof x) // "string"

使用 instanceof 进行收窄

JavaScript 的 instanceof 运算符用于检查某个值是否是某个类的实例。它以 value instanceof value2 的格式使用,并返回一个布尔值。当检查某个值是否为类的实例 instanceof 时,TypeScript 会将该类型分配给变量,从而收窄类型。

以下面的示例为例,其中一个函数接受一个日期,可以是字符串或日期。如果是日期,我们想将其转换为字符串,如果是字符串,我们将按原样返回。我们可以使用 instanceof 检查它是否是 Date 的实例,并将其转换为字符串,如下所示。

function dateToString(value: string | Date) {
  if (value instanceof Date) {
    // 现在的类型是 Date,您可以访问 Date 方法
    return value.toISOString()
  }
  return value
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容