TypeScript 联合类型(union type)

2023-10-27

TS是JS的超集,在JS的基础上添加了一套类型系统,这样的TS可以被静态分析带来的好处显而易见。

let val: string = 'val';

声明一个string类型的变量val

let val: string = 'val';
val = 1; // Type 'number' is not assignable to type 'string'.

因为number类型和string类型并不兼容,在string类型值出现的地方并不能使用number类型指完成替换,所以在TS世界中给string类型的val变量赋值number类型的值会报错。

但是在JS中并没有赋值的限制:

// javascript
var val = 'val';
val = 1; // 这里不会报错

在JS中变量val首先被赋值了字符串,然后被赋值了数字,这两个数据类型并不一致,但是因为JS没有静态类型检查,所以这并不会报错。

TS的联合类型可以适应这种情况,表示这个变量可能是类型a也可能是类型b也可能是类型c等类型。

let val: string | number = 'val'
val = 1 // 这里并不会报错

在TS中,这里声明的变量val则表示可能是类型string也可能是类型number,所以对变量赋值string类型值和number类型值,并不会报错。

但问题也随之而来。

function test(val: string | number) {
  val.toLowerCase() // Property 'toLowerCase' does not exist on type 'string | number'.
}

TS会提示类型 string | number 上没有属性toLowserCase。

这个报错也很容易理解,因为类型string的数据上可以调用方法toLowerCase,但是number不可以。因为变量val的类型,string和number都有可能,TS并不能确定val在运行时是string类型,所以会出现这个错误。

我们需要一个方法来告诉TS这个变量现在是string类型。

使用typeof缩小类型范围

对于上面例子中的变量val.toLowerCase的报错来说是因为val的类型范围有点大(string | number),如果我们通过某种方式缩小该范围为string,那么在val上访问属性toLowserCase应当没有问题。

function test(val: string | number) {
  if (typeof val === 'string') {
    val.toLowerCase() // 这里并不会报错
    console.log('val是字符串')
  } else {
    console.log('val是数字')
  }
}

在TS中,if中的typeof val === 'string'这种形式的代码会被识别为类型保护(type guard)。TS会分析代码的执行流程,缩小变量可能的类型。在分支if (typeof val === ‘string’) 中变量val的类型被TS识别为’string’,所以在这个分支下val调用string类型数据的方法并不会报错。

因为val是string和number这两个类型的联合,TS不仅知道if子句中的val是string类型,还知道else中的val是number类型。

可以被typeof进行类型保护的类型有:

  • “string”
  • “number”
  • “bigint”
  • “boolean”
  • “symbol”
  • “undefined”
  • “object”
  • “function”

使用in缩小类型范围

上面介绍了对于基础类型的识别,在TS中使用频率更多的还有对象,使用typeof来区别不同对象显然是有问题的,因为typeof出来的结果都是’object’无法分辨两个不同的对象。

type A = {a: string}
type B = {b: string}

function test(val: A | B) {
  val.a // Property 'a' does not exist on type 'A | B'
}

在test函数中无论是访问val.a还是val.b都会报错,而原因已经明白,TS无法确定变量val的具体类型,TS并不知道当前是类型A还是类型B,所以我们需要帮他一把。

type A = {a: string}
type B = {b: string}

function test(val: A | B) {
  if ('a' in val) {
    console.log(val.a)
    return
  }
  console.log(val.b)
}

其中的in操作同样会被TS识别为类型保护,如果属性a存在于变量val中那么就能识别出val变量是类型A,则可以正常访问类型A中存在的属性a。

使用instanceof缩小类型范围

对于对象类型的区分除了使用操作符in还可以使用instanceof来完成。

function test(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString());
  } else {
    console.log(x.toUpperCase());
  }
}

使用 === 和 == 缩小类型范围

使用严格等于(===)也可以在某些特别情况下正确缩小类型范围

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // We can now call any 'string' method on 'x' or 'y'.
    x.toUpperCase();

    y.toLowerCase();
  } else {
    console.log(x); // x 是 string | number

    console.log(y); // y 是 string | boolean
  }
}

在这个例子中xstringnumber的联合,而y是stringboolean的联合,当x === y的时候只可能xy都是string类型。所以在分支if (x === y) 中x和y的类型被正确识别为string类型。

我们知道在JS中宽松等于(== null)可以匹配null和undefined两种类型,当然TS也知道,所以 ==null,可以被用来识别类型。

interface Container {
  value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
  // Remove both 'null' and 'undefined' from the type.
  if (container.value != null) {
    console.log(container.value);
    // Now we can safely multiply 'container.value'.
    container.value *= factor;
  }
}

即使container.value可能是null或者undefined类型,但是在分支container.value != null中,该变量类型就只可能是number类型,所以其参与算术运算并不会报错。

区分联合类型

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;
  }
}

TS会通过参与联合的类型都有的属性kind来识别当前shape是Circle或者Square达到类型保护的目的。

类型谓词

上面介绍了TS对于联合类型中基础类型和对象类型的类型保护。但是这并不能覆盖全部的场景,例如上面介绍到的内容,并不能区分两个函数:

type fn1 = (arg: number) => boolean
type fn2 = (arg: string) => boolean

function test(fn: fn1 | fn2) {
  return fn(1)
}

在这个例子里,我们并没有方法来识别fn是类型fn1还是类型fn2。

在前面的例子里,例如 if (typeof a === ‘string’) 这里面的变量a会被TS类型系统识别为string,如果TS将识别一个变量为某个类型的能力开放给开发者,上面的问题就会迎刃而解。这个能力就是类型谓词。

通过观察上面类型保护的规律就会发现TS总会询问:变量a是类型A吗?被识别为类型保护的操作的回答总是true或者false都是boolean值。typoef a === ‘string’或者’a’ in A或者a instanceof A,这些操作的返回值都是boolean,并且都是和特定类型作比较。

// 我就是类型谓词形成的类型保护
function isTypefn1(fn: fn1 | fn2): fn is fn1 {
  if (fn.name === 'fn1') return true
  return false
}
function test(fn: fn1 | fn2) {
  if (isTypefn1(fn)) return fn(1)
  else return fn('1')
}

其中 isTypefn1调用的返回值就会告诉TS入参是不是类型fn1,这样TS就可以识别变量fn的类型完成类型保护。

参考

Narrowing

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TypeScript 联合类型(union type) 的相关文章

随机推荐

  • 一篇关于运放自激振荡的帖子

    在负反馈电路时 反馈系数F越小越可能不产生自激震荡 换句话说 F越大 产生自激震荡的可能性越大 对于电阻反馈网络 F的最大值是1 F 1的典型电路就是电压跟随电路 这就是电压跟随运放易震荡原因 这也是我们常常会看到运放手册标有单位增益稳定说
  • Python 下中文分词算法的简单示例

    代码如下 import jieba 需要先安装jieba库 text Python中文分词是一个很好用的工具 在自然语言处理领域得到了广泛应用 待分词的文本 使用jieba进行分词 cut方法返回一个生成器 遍历该生成器可以得到每个词语 s
  • Python3.5(ANCONDA3)连接MYSQL数据库

    Python3和Python2连接MYSQL数据库稍微不同 我们这里要使用PyMySQL 什么是 PyMySQL PyMySQL 是在 Python3 x 版本中用于连接 MySQL 服务器的一个库 Python2中则使用mysqldb P
  • String字符串常量池

    首先看一段这样的代码 String str1 abc String str2 abc String str3 new String abc System out println str1 str2 System out println st
  • caffe学习(二):利用mnist数据集训练并进行手写数字识别(windows)

    准备数据集 http yann lecun com exdb mnist 提供了训练集与测试集数据的下载 但是caffe并不是直接处理这些数据 而是要转换成lmdb或leveldb格式进行读取 如何转换可以再去查阅相关资料 为简化步骤 直接
  • 【CVPR小目标检测】- ISNet红外小目标检测

    ISNet 红外小目标探测的形状问题 从分割的角度完成小目标红外检测 红外图像 红外小目标使用红外热成像技术 使得红外目标检测能够全天候工作 可视距离远 抗干扰能力强 当像素距离较远时 目标所占比例小 亮度低 呈现弱小目标 红外图像中 弱小
  • 【毕业设计】图像检索算法(以图搜图)

    文章目录 1 前言 2 图像检索介绍 2 1 无监督图像检索 2 2 有监督图像检索 3 图像检索步骤 4 应用实例 5 最后 1 前言 Hi 大家好 这里是丹成学长的毕设系列文章 对毕设有任何疑问都可以问学长哦 这两年开始 各个学校对毕设
  • 线性代数的学习和整理2:什么是线性,线性相关,线性无关 以及什么是线性代数?

    目录 1 写在前面的话 1 1 为什么要先总结一些EXCEL计算矩阵的工具性知识 而不是一开始就从基础学起呢 1 2 关于线性代数入门时的各种灵魂发问 1 3 学习资料 2 什么是线性 关系 2 1 线性的到底是一种什么关系 线性关系 正比
  • Q-learning和Sarsa

    一 Q learning Q Learning的目的是学习特定state下 特定action的价值 是建立一个Q table 以state为行 action为列 通过每个动作带来的奖赏更新Q table 是异策略 行动策略和评估策略不是一个
  • Java实验报告(四)

    文章目录 题目一 题目二 题目三 题目四 题目五 题目六 题目七 题目一 一 程序一 源程序 public class Leaf int i 0 Leaf increment i return this void print System
  • 除了敲代码,程序员还能有什么副业?晓健韩品批发

    积极发展副业中 做韩妆一般贸易进口 跟集装箱通关的 仓库在山东烟台莱山区 做的比较大 价格方面有优势 有需要 可以私聊哈 正品是本分 xiaojian675050734 H19950211H
  • composer 中国镜像

    我的个人博客 逐步前行STEP 使用以下代码将packagist源更换为中国镜像 composer config g repo packagist composer https packagist phpcomposer com 或者 co
  • 【满分】【华为OD机试真题2023 JAVA&JS】不爱施肥的小布

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 不爱施肥的小布 知识点二分查找 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 某农场主管理了一大片果园 fields i 表示不同果林的面积 单位 m 2 现在
  • targetElement.closest

    在组件挂载时 给 document 添加点击事件监听 onMounted gt document addEventListener click closeIdentity 点击事件处理函数 用于改变 isExpanded 的值为 false
  • Lock wait timeout exceeded; try restarting transaction

    2016 08 05 09 21 52 com zhishi common controller BaseController ERROR Error updating database Cause com mysql jdbc excep
  • 教师怎样将成绩单独发送给学生和家长?

    在工作学习中 我们经常会遇到怎样将成绩单独发送给学生和家长这样的问题 因此 面对怎样将成绩单独发送给学生和家长我们应该有努力探索的精神 诚挚的心灵 是学生情感的钥匙 高尚的师德 是学生心灵的明镜 对于这个问题也是一样的 读书忌死读 死读钻牛
  • redhat7.6安装weblogic12c

    目录 一 环境准备 二 使用root创建用户和组 三 创建部署目录 四 上传安装包 五 创建 oraInst loc 文件 六 创建wls rsp 响应文件 七 进行安装 八 使用 wlst sh 离线模式创建一个域 九 启动服务 十 浏览
  • 电脑wps可以语音录入吗_怎样用word进行语音录入文字

    怎样用 word 进行语音录入文字 有时候我们不方便手敲键盘输入时 可以利用 word 的语音录入功能进行输入 以下是学习啦小编为您带来的关于用 Word 进行语音录入文字 希望对您有所帮助 用 Word 进行语音录入文字 语音输入功能添加
  • 最适合程序猿的笔记软件

    因为这几天小编要去听课 所以心血来潮找了几个适合程序猿的笔记软件 经过几天在csdn上的扒饭之后 我结合自己的某些要求 为大家整理出了这几个软件 有需要直接去后面找 一 必须支持markdown markdown的重要性不需要在这里多说了吧
  • TypeScript 联合类型(union type)

    TS是JS的超集 在JS的基础上添加了一套类型系统 这样的TS可以被静态分析带来的好处显而易见 let val string val 声明一个string类型的变量val let val string val val 1 Type numb