TypeScript中 typeof ArrayInstance[number] 剖析
假设这样一个场景,目前业务上仅对接了三方支付 '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
是数组实例,number
是 TypeScript
数字类型。若是 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 解析规则如下:
- 如果 X 包含字符串索引签名, keyof X 则是由string 、number 类型, 以及symbol-like 属性字面量类型组成的联合类型, 否则
- 如果 X 包含数字索引签名, keyof X 则是由number类型 , 以及string-like 、symbol-like 属性字面量类型组成的联合类型, 否则
- keyof X 由 string-like, number-like, and symbol-like 属性字面量类型组成的联合类型.
其中
- 对象类型的 string-like 属性可以是 an identifier, a string literal, 或者 string literal type的计算属性名 .
- 对象类型的number-like 属性可以是 a numeric literal 或 numeric literal type 的计算属性名.
- 对象类型的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]
从常量值数组中获取对应元素字面量类型 的剖析至此结束 。
TypeScript中 typeof ArrayInstance[number] 剖析的更多相关文章
- JavaScript中typeof、toString、instanceof、constructor与in
JavaScript 是一种弱类型或者说动态语言.这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定. 这也意味着你可以使用同一个变量保存不同类型的数据. 最新的 ECMAScrip ...
- 【JavaScript中typeof、toString、instanceof、constructor与in】
JavaScript中typeof.toString.instanceof.constructor与in JavaScript 是一种弱类型或者说动态语言.这意味着你不用提前声明变量的类型,在程序运行 ...
- TypeScript 中的方法重载
方法重载(overload)在传统的静态类型语言中是很常见的.JavaScript 作为动态语言, 是没有重载这一说的.一是它的参数没有类型的区分,二是对参数个数也没有检查.虽然语言层面无法自动进行重 ...
- typeScript中的函数
// 函数的定义 //es5定义函数的方法 /* //函数声明法 function run(){ return 'run'; } //匿名函数 var run2=function(){ return ...
- 聊聊 TypeScript 中的类型保护
聊聊 TypeScript 中的类型保护 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird { // 独有方法 fly(); // 共有方法 lay ...
- TypeScript 中限制对象键名的取值范围
当我们使用 TypeScript 时,我们想利用它提供的类型系统限制代码的方方面面,对象的键值,也不例外. 譬如我们有个对象存储每个年级的人名,类型大概长这样: type Students = Rec ...
- typescript 中的 infer 关键字的理解
infer 这个关键字,整理记录一下,避免后面忘记了.有点难以理解呢. infer infer 是在 typescript 2.8中新增的关键字. infer 可以在 extends 条件类型的字句中 ...
- TypeScript 中函数的理解?与 JavaScript 函数的区别?
一.是什么 函数是JavaScript 应用程序的基础,帮助我们实现抽象层.模拟类.信息隐藏和模块 在TypeScript 里,虽然已经支持类.命名空间和模块,但函数仍然是主要定义行为的方式,Type ...
- 5种在TypeScript中使用的类型保护
摘要:在本文中,回顾了TypeScript中几个最有用的类型保护,并通过几个例子来了解它们的实际应用. 本文分享自华为云社区<如何在TypeScript中使用类型保护>,作者:Ocean2 ...
随机推荐
- hdp、cdh版本大数据maven仓库
Hdp版本: <repositories> <repository> <id>nexus</id> <name>local private ...
- vue的路由组件挂载。
vue通过多种方式可以将组件挂载到一个页面上.挂载方式有四种.其实也并不止四种.这里呢就简单的提四种方式去怎样挂载组件. 第一种就是作为标签形式挂载.前面也提到. 后面的就是一般的挂载组件和按需挂载组 ...
- docker搭建前端环境
开发环境的搭建,是新人入职后的第一道槛,有时一个小小的问题就能阻塞半天.如果能提供一个工具在短时间内搞定开发环境,势必提高新人对团队的印象分!docker就是这样一个工具. 镜像&容器 doc ...
- 【剑指 Offer】09.用两个栈实现队列
题目描述 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead , 分别完成在队列尾部插入整数和在队列头部删除整数的功能.(若队列中没有元素,del ...
- XSS-labs通关挑战(xss challenge)
XSS-labs通关挑战(xss challenge) 0x00 xss-labs 最近在看xss,今天也就来做一下xss-labs通过挑战.找了好久的源码,终于被我给找到了,因为在GitHub上 ...
- SQL查找连续出现的数字
基于Oracle: 题:编写一个 SQL 查询,查找所有至少连续出现三次的数字. +----+-----+ | Id | Num | +----+-----+ | 1 | 1 | | 2 | 1 | ...
- (十九)hashlib模块
hashlib模块用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 注意:md5和sha25 ...
- ps ww
[root@ma ~]# ps ww -p 1 PID TTY STAT TIME COMMAND 1 ? Ss 0:01 /sbin/init[root@ma ~]# ps -p 1 PID TTY ...
- Netty学习:伪共享
目录 Netty中的伪共享 伪共享的原理以及介绍 Netty中的伪共享 先说为什么知道这个概念吧,期初看Netty源码的时候,看到了NioEventLoop的构建,其中有这么一句代码: private ...
- Pulsar 社区周报|2021-01-11~2021-01-17
Pulsar 周报由 StreamNative 翻译整理.原文内容来自 StreamNative 官网 Pulsar 周报模块. 本期编辑:Tango@StreamNative. 关于 Apache ...