假设这样一个场景,目前业务上仅对接了三方支付 'Alipay', 'Wxpay', 'PayPal'
, 实际业务 getPaymentMode
会根据不一样支付方式进行不一样的付款/结算流程。html
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
类型,没法避免因为手误或上层业务处理传参不当引发的运行时逻辑错误。git
能够经过声明字面量联合类型来解决上述问题。github
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)
字面量联合类型虽然解决了问题,可是须要保持值数组和联合类型之间的同步,且存在冗余。typescript
二者声明在同一个文件时,问题尚且不大。若 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]
完美的解决了上述问题,经过数组值获取对应类型。ide
typeof ArrayInstance[number] 如何拆解
首先能够肯定 type mode = typeof PAYMENT_MODE[number]
在 TypeScript
类型声明上下文 ,而非 JavaScript
变量声明上下文。函数
PAYMENT_MODE
是数组实例,number
是 TypeScript
数字类型。如果 PAYMENT_MODE[number]
组合,则语法不正确,数组实例索引操做 []
中只能具体数字, 不能是类型。url
因此 typeof PAYMENT_MODE[number]
等同于 (typeof PAYMENT_MODE)[number]
。es5
能够看出 typeof PAYMENT_MODE
是一个数组类型spa
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": Function; //省略其他数组方法属性 ..... } type eleType = payType[number] // "Alipay" | "Wxpay" | "PayPal"
后来我在 lib.es5.d.ts 中找到了 ReadonlyArray<T> 类型,更进一步验证了上面的推测:
interface ReadonlyArray<T> { readonly length: number; toString(): string; //......省略中间函数 readonly [n: number]: T; }
payType
中多出的 "0", "1"等字符串索引属性猜想是因为 as const
断言为常量类型,编译期自动生成的,相关实现还未找到,往后遇到文档再补充。
借助 typeof ArrayInstance[number]
从常量值数组中获取对应元素字面量类型 的剖析至此结束 。