假设这样一个场景,目前业务上仅对接了三方支付 'Alipay', 'Wxpay', 'PayPal', 实际业务 getPaymentMode 会根据不同支付方式进行不同的付款/结算流程。

const PAYMENT_MODE = ['Alipay', 'Wxpay', 'PayPal'];

function getPaymentMode(paymode: string) {
return PAYMENT_MODE.find(thirdPay => thirdPay === paymode)
} getPaymentMode('Alipay') // ️
getPaymentMode('Wxpay') // ️
getPaymentMode('PayPal') // ️
getPaymentMode('unknow') // ️ 正常编译,但可能引发运行时逻辑错误

由于声明仅约束了入参 string 类型,无法避免由于手误或上层业务处理传参不当引起的运行时逻辑错误。

可以通过声明字面量联合类型来解决上述问题。

const PAYMENT_MODE = ['Alipay', 'Wxpay', 'PayPal'];
type mode = 'Alipay' | 'Wxpay' | 'PayPal'; function getPaymentMode(paymode: mode) {
return PAYMENT_MODE.find(thirdPay => thirdPay === paymode)
} getPaymentMode('Alipay') // ️
getPaymentMode('Wxpay') // ️
getPaymentMode('PayPal') // ️
getPaymentMode('unknow') // Argument of type '"unknow"' is not assignable to parameter of type 'mode'.(2345)

字面量联合类型虽然解决了问题,但是需要保持值数组和联合类型之间的同步,且存在冗余。

两者声明在同一个文件时,问题尚且不大。若 PAYMENT_MODE 由第三方库提供,对方非 TypeScript 技术栈无法提供类型文件,那要保持同步就比较困难,新增支付类型或支付渠道合作终止,都会引入潜在风险。

const PAYMENT_MODE = ['Alipay', 'Wxpay', 'PayPal'] as const; //亦可 import { PAYMENT_MODE } from 'outer'
type mode = typeof PAYMENT_MODE[number] // "Alipay" | "Wxpay" | "PayPal" 1) function getPaymentMode(paymode: mode) {
return PAYMENT_MODE.find(thirdPay => thirdPay === paymode)
} getPaymentMode('Alipay') // ️
getPaymentMode('Wxpay') // ️
getPaymentMode('PayPal') // ️
getPaymentMode('unknow') // Argument of type '"unknow"' is not assignable to parameter of type '"Alipay" | "Wxpay" | "PayPal"'.

1)处引入了本文的主角 typeof ArrayInstance[number] 完美的解决了上述问题,通过数组值获取对应类型


typeof ArrayInstance[number] 如何拆解

首先可以确定 type mode = typeof PAYMENT_MODE[number] TypeScript 类型声明上下文 ,而非 JavaScript 变量声明上下文。

PAYMENT_MODE 是数组实例,numberTypeScript数字类型。若是 PAYMENT_MODE[number] 组合,则语法不正确,数组实例索引操作 [] 中只能具体数字, 不能是类型。

所以 typeof PAYMENT_MODE[number] 等同于 (typeof PAYMENT_MODE)[number]

可以看出 typeof PAYMENT_MODE 是一个数组类型

type mode1 = typeof PAYMENT_MODE //  readonly ["Alipay", "Wxpay", "PayPal"]

typeof PAYMENT_MODE[number] 等效 mode1[number] ,我们知道 mode1[] indexed access types[]Index 来源于 Index Type Query 也即 keyof 操作 。

type mode1 =keyof typeof PAYMENT_MODE
// number | "0" | "1" | "2" | "length" | "toString" | "toLocaleString" | "concat" | "join" | "slice" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | ... 7 more ... | "includes"

可以看出得到的联合类型第一项就是 number 类型,我们常见 keyof 得到的都是类型属性名组成的字符串字面量联合类型,如下所示,那这个 number 是怎么来的。

interface Person {
name: string;
age: number;
location: string;
} type K1 = keyof Person; // "name" | "age" | "location"

TypeScript-2.9 文档可以看出,

如果 X 是对象类型, keyof X 解析规则如下:

  1. 如果 X 包含字符串索引签名, keyof X 则是由string 、number 类型, 以及symbol-like 属性字面量类型组成的联合类型, 否则
  2. 如果 X 包含数字索引签名, keyof X 则是由number类型 , 以及string-like 、symbol-like 属性字面量类型组成的联合类型, 否则
  3. keyof X 由 string-like, number-like, and symbol-like 属性字面量类型组成的联合类型.

其中

  1. 对象类型的 string-like 属性可以是 an identifier, a string literal, 或者 string literal type的计算属性名 .
  2. 对象类型的number-like 属性可以是 a numeric literal 或 numeric literal type 的计算属性名.
  3. 对象类型的symbol-like 属性可以是a unique symbol type的计算属性名.

示例如下:

const c = "c1";
const d = 10;
const e = Symbol(); const enum E1 {
A
}
const enum E2 {
A = "A"
} type Foo1 = {
"f": string, // String-like 中 a string literal
["g"]:string; // String-like 中 计算属性名
a: string; // String-like 中 identifier
[c]: string; // String-like 中 计算属性名
[E2.A]: string; // String-like 中计算属性名 5: string; // Number-like 中 numeric literal
[d]: string; // Number-like 中 计算属性名
[E1.A]: string; // Number-like 中 计算属性名 [e]: string; // Symbol-like 中 计算属性名
}; type K11 = keyof Foo1; // type K11 = "c1" | E2.A | 10 | E1.A | typeof e | "f" | "g" | "a" | 5

再次回到前面内容:

type payType = typeof PAYMENT_MODE; // readonly ["Alipay", "Wxpay", "PayPal"]

type mode1 =keyof typeof PAYMENT_MODE
// number | "0" | "1" | "2" | "length" | "toString" | "toLocaleString" | "concat" | "join" | "slice" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | ... 7 more ... | "includes"

编译器提示的 readonly ["Alipay", "Wxpay", "PayPal" 类型不够具象,我们无从得知 payType 具体有哪些属性。

keyof typeof PAYMENT_MODE 只有 number 类型而没有 string 类型,根据上面 keyof 解析规则的第2条,可以推断 typeof PAYMENT_MODE 类型含有数字索引签名,以及之前的结果 type mode = typeof PAYMENT_MODE[number] // "Alipay" | "Wxpay" | "PayPal"

我们可以据此推测出 payType 更加直观的类型结构:

type payType = {
[i :number]: "Alipay" | "Wxpay" | "PayPal"; //数字索引签名
"length": number;
"0": "Alipay"; //因为数组可以通过数字或字符串访问
"1": "Wxpay";
....
"toString": string;
//省略其余数组方法属性
.....
} type eleType = payType[number] // "Alipay" | "Wxpay" | "PayPal"

后来我在 lib.es5.d.ts 中找到了 ReadonlyArray 类型,更进一步验证了上面的推测:

interface ReadonlyArray<T> {
readonly length: number;
toString(): string;
//......省略中间函数
readonly [n: number]: T;
}

值得一提的是,ReadonlyArray 类型结构中,没有常规数组 push 等写操作方法名的 key

const immutable = ['a', 'b', 'c'] as const;
immutable[2]; //️
immutable[4]; // // length '3' has no element at index '4'
immutable.push ;// //Property 'push' does not exist on type 'readonly ["a", "b", "c"]'
immutable[0] = 'd'; // Cannot assign to '0' because it is a read-only property const mutable = ['a', 'b', 'c'] ;
mutable[2]; //️
mutable[4]; //️
mutable.push('d'); //️

由于数组是对象,所以 mutable 是引用,即使用const声明变量, 依然可以修改数组中元素。得益于as const的类型断言,编译期可以确定ReadonlyArray 类型,无法修改数组,编译器就可以动态生成如下类型。

type indexLiteralType = {
"0": "Alipay" ;
"1": "Wxpay";
"2": "PayPal";
}

按照设计模式中接口单一职责原则, 可以推断 payType (readonly ["Alipay", "Wxpay", "PayPal"]) 是由ReadonlyArray 只读类型和 indexLiteralType 字面量类型组成的联合类型。

type indexLiteralType = {
readonly "0": "Alipay" ,
readonly "1": "Wxpay",
readonly "2": "PayPal"
};
type values = indexLiteralType [keyof indexLiteralType ];
type payType = ReadonlyArray<values> & indexLiteralType; type test1 = payType extends (typeof PAYMENT_MODE) ? true:false; //false
type test2 = (typeof PAYMENT_MODE) extends payType ? true:false; //true type test3 = payType[number] extends (typeof PAYMENT_MODE[number]) ? true:false; //true
type test4 = (typeof PAYMENT_MODE[number]) extends payType[number] ? true:false; //true

这里我们构造出的 payType typeof PAYMENT_MODE 的父类型,已经非常接近了,还需要再和其他类型进行联合才能得到一样的类型,现在 payType 的具象程度已经足够我们理解typeof PAYMENT_MODE了,不再进一步构造一样的类型,因目前掌握的信息可能无法构造完全一样的类型。

借助 typeof ArrayInstance[number] 从常量值数组中获取对应元素字面量类型 的剖析至此结束 。

示例地址 Playground

TypeScript中 typeof ArrayInstance[number] 剖析的更多相关文章

  1. JavaScript中typeof、toString、instanceof、constructor与in

    JavaScript 是一种弱类型或者说动态语言.这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定. 这也意味着你可以使用同一个变量保存不同类型的数据. 最新的 ECMAScrip ...

  2. 【JavaScript中typeof、toString、instanceof、constructor与in】

    JavaScript中typeof.toString.instanceof.constructor与in JavaScript 是一种弱类型或者说动态语言.这意味着你不用提前声明变量的类型,在程序运行 ...

  3. TypeScript 中的方法重载

    方法重载(overload)在传统的静态类型语言中是很常见的.JavaScript 作为动态语言, 是没有重载这一说的.一是它的参数没有类型的区分,二是对参数个数也没有检查.虽然语言层面无法自动进行重 ...

  4. typeScript中的函数

    // 函数的定义 //es5定义函数的方法 /* //函数声明法 function run(){ return 'run'; } //匿名函数 var run2=function(){ return ...

  5. 聊聊 TypeScript 中的类型保护

    聊聊 TypeScript 中的类型保护 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird { // 独有方法 fly(); // 共有方法 lay ...

  6. TypeScript 中限制对象键名的取值范围

    当我们使用 TypeScript 时,我们想利用它提供的类型系统限制代码的方方面面,对象的键值,也不例外. 譬如我们有个对象存储每个年级的人名,类型大概长这样: type Students = Rec ...

  7. typescript 中的 infer 关键字的理解

    infer 这个关键字,整理记录一下,避免后面忘记了.有点难以理解呢. infer infer 是在 typescript 2.8中新增的关键字. infer 可以在 extends 条件类型的字句中 ...

  8. TypeScript 中函数的理解?与 JavaScript 函数的区别?

    一.是什么 函数是JavaScript 应用程序的基础,帮助我们实现抽象层.模拟类.信息隐藏和模块 在TypeScript 里,虽然已经支持类.命名空间和模块,但函数仍然是主要定义行为的方式,Type ...

  9. 5种在TypeScript中使用的类型保护

    摘要:在本文中,回顾了TypeScript中几个最有用的类型保护,并通过几个例子来了解它们的实际应用. 本文分享自华为云社区<如何在TypeScript中使用类型保护>,作者:Ocean2 ...

随机推荐

  1. i5 11300H 怎么样 相当于什么水平

    i5-11300H 为 4 核 8 线程,主频 3.1GHz,睿频 4.4GHz,三级缓存 8MBi5-11300H 怎么样看完你就知道了 https://list.jd.com/list.html?

  2. .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ HelloWorld)--学习笔记

    2.6.3 RabbitMQ -- HelloWorld 发送端 接收端 rabbitmq container 发送信息 https://www.rabbitmq.com/tutorials/tuto ...

  3. 【递归】P1157组合的输出

    题目相关 题目描述 排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且 r ≤n),我们可以简单地将n个元素理解为自然数1,2,-,n从中任取r个数. 现要求你输出所有组合. ...

  4. WPF Line 的颜色过度动画

    <Line Grid.Column="2" Grid.ColumnSpan="2" VerticalAlignment="Center" ...

  5. layui城市三级联动(fesiong / layarea)

    安装 GitHub下载地址:https://github.com/fesiong/layarea.git 使用(html+js) 1. html部分 整个选择器需要使用一个父标签包裹,如下使用了id= ...

  6. python模块/文件/日期时间

    文件操作:

  7. AVA编程中button按钮,actionlistener和mouseClicked区别

    在java的编程中,对于按钮button 有两个事件: 1.actionPerformed 2.mouseClicked 区别: actionPerformed:一般事件,仅侦听鼠标左键的单击事件,右 ...

  8. bean与map之间的转化

    import java.util.HashMap; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; impor ...

  9. Graph Explore的使用介绍

    我在Graph API开发中用的最多的测试工具就是Graph Explore,这个是微软开发的网页版的Graph API的测试工具,能满足我大部分需求. 访问网址是:Graph Explorer - ...

  10. Numpy的一些学习记录

    Numpy的一些记录 产生numpy.array的方式 import numpy as np arr1 = np.array([1, 2, 3]) print(arr1) arr2 = np.zero ...