TypeScript中 typeof ArrayInstance[number] 剖析
2021-02-04 18:51
981 查看
假设这样一个场景,目前业务上仅对接了三方支付
'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 解析规则如下:
- 如果 X 包含字符串索引签名, keyofX 则是由string、number类型, 以及symbol-like 属性字面量类型组成的联合类型, 否则
- 如果 X 包含数字索引签名, keyofX 则是由number类型 , 以及string-like 、symbol-like 属性字面量类型组成的联合类型, 否则
- keyofX 由 string-like, number-like, and symbol-like 属性字面量类型组成的联合 56c 类型.
其中
- 对象类型的 string-like 属性可以是 an identifier, a stringliteral, 或者 stringliteral 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 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" | "i 103c ndexOf" | "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 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 [keyofindexLiteralType ]; type payType= ReadonlyArray<values> & indexLiteralType; type test1 = payTypeextends (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]
从常量值数组中获取对应元素字面量类型 的剖析至此结束 。
相关文章推荐
- instanceof typeof isArray
- How to change instance_number of RAC
- The "data" argument must be one of type string, TypedArray, or DataView. Received type number
- [Error]No enclosing instance of type XX is accessible. Must qualify the allocation with an e
- No enclosing instance of type XmlTest is accessible. Must qualify the alloca
- java编译错误处理No enclosing instance of type demo1_1 is accessible.
- java中"no enclosing instance of type * is accessible"的解决方法
- No enclosing instance of type E is accessible. Must qualify the allocation with an enclosing
- LinkageError之loader (instance of xxx) previously initiated loading for a different type with name "lib/MyData"
- Java出现No enclosing instance of type E is accessible. Must qualify the allocation with an enclosing
- [LeetCode] Search for a Range (sorted integers array,find start & end position of a target number)
- Data Structure Array: Largest subarray with equal number of 0s and 1s
- Cannot use object of type stdClass as array
- Java出现No enclosing instance of type E is accessible. Must qualify the allocation with an enclosing
- PHP JSON出错:Cannot use object of type stdClass as array解决方法
- Java 程序报错“No enclosing instance of type AA is accessible. Must qualify the allocation with an enclo”
- PHP错误Cannot use object of type stdClass as array in错误的解决办法
- Java出现No enclosing instance of type E is accessible. Must qualify the allocation with an enclosing
- Java在编译运行时出现No enclosing instance of type XXX is accessible问题
- Flex的四种判断对象类型的方式 as instanceof is typeof比较