假设这样一个场景,目前业务上仅对接了三方支付 '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. hdp、cdh版本大数据maven仓库

    Hdp版本: <repositories> <repository> <id>nexus</id> <name>local private ...

  2. vue的路由组件挂载。

    vue通过多种方式可以将组件挂载到一个页面上.挂载方式有四种.其实也并不止四种.这里呢就简单的提四种方式去怎样挂载组件. 第一种就是作为标签形式挂载.前面也提到. 后面的就是一般的挂载组件和按需挂载组 ...

  3. docker搭建前端环境

    开发环境的搭建,是新人入职后的第一道槛,有时一个小小的问题就能阻塞半天.如果能提供一个工具在短时间内搞定开发环境,势必提高新人对团队的印象分!docker就是这样一个工具. 镜像&容器 doc ...

  4. 【剑指 Offer】09.用两个栈实现队列

    题目描述 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead , 分别完成在队列尾部插入整数和在队列头部删除整数的功能.(若队列中没有元素,del ...

  5. XSS-labs通关挑战(xss challenge)

    XSS-labs通关挑战(xss challenge) 0x00 xss-labs   最近在看xss,今天也就来做一下xss-labs通过挑战.找了好久的源码,终于被我给找到了,因为在GitHub上 ...

  6. SQL查找连续出现的数字

    基于Oracle: 题:编写一个 SQL 查询,查找所有至少连续出现三次的数字. +----+-----+ | Id | Num | +----+-----+ | 1 | 1 | | 2 | 1 | ...

  7. (十九)hashlib模块

    hashlib模块用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 注意:md5和sha25 ...

  8. 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 ...

  9. Netty学习:伪共享

    目录 Netty中的伪共享 伪共享的原理以及介绍 Netty中的伪共享 先说为什么知道这个概念吧,期初看Netty源码的时候,看到了NioEventLoop的构建,其中有这么一句代码: private ...

  10. Pulsar 社区周报|2021-01-11~2021-01-17

    Pulsar 周报由 StreamNative 翻译整理.原文内容来自 StreamNative 官网 Pulsar 周报模块. 本期编辑:Tango@StreamNative. 关于 Apache ...