AI智能
改变未来

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 =keyoftypeof 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 = keyofPerson; // \"name\" | \"age\" | \"location\"

从 TypeScript-2.9 文档可以看出,

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

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

其中

  1. 对象类型的 string-like 属性可以是 an identifier, a stringliteral, 或者 stringliteral 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 stringliteral[\"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 = keyofFoo1; // 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 =keyoftypeof PAYMENT_MODE// number| \"0\" | \"1\" | \"2\" | \"length\" | \"toString\" | \"toLocaleString\" | \"concat\" | \"join\" | \"slice\" | \"i103cndexOf\" | \"lastIndexOf\" | \"every\" | \"some\" | \"forEach\" | \"map\" | \"filter\" | ... 7 more ... | \"includes\"

编译器提示的

readonly [\"Alipay\", \"Wxpay\", \"PayPal\"

类型不够具象,我们无从得知

payType

具体有哪些属性。

keyoftypeof 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 propertyconst 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 [keyofindexLiteralType ];type payType= ReadonlyArray<values> & indexLiteralType;type test1 = payTypeextends (typeof PAYMENT_MODE) ? true:false; //falsetype test2 = (typeof PAYMENT_MODE) extends payType? true:false; //truetype test3 = payType[number] extends (typeof PAYMENT_MODE[number]) ? true:false; //truetype test4 = (typeof PAYMENT_MODE[number]) extends payType[number] ? true:false; //true

这里我们构造出的

payType

typeof PAYMENT_MODE

的父类型,已经非常接近了,还需要再和其他类型进行联合才能得到一样的类型,现在

payType

的具象程度已经足够我们理解

typeof PAYMENT_MODE

了,不再进一步构造一样的类型,因目前掌握的信息可能无法构造完全一样的类型。

借助

typeof ArrayInstance[number]

从常量值数组中获取对应元素字面量类型 的剖析至此结束 。

示例地址 Playground

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » TypeScript中 typeof ArrayInstance[number] 剖析