Typescript 函数,它从具有正确类型的数组中返回一项
Posted
技术标签:
【中文标题】Typescript 函数,它从具有正确类型的数组中返回一项【英文标题】:Typescript function which returns one item from an array with proper type 【发布时间】:2020-04-19 09:51:54 【问题描述】:我有一个从数组中返回一项的函数:
function oneItem(arr: any[])
return arr[~~(Math.random() * arr.length)];
我想为此提供合适的类型。
我的尝试:
type ValuesOf<T extends any[]>= T[number];
function oneItem<ARRAY extends any[]>(arr: ARRAY): ValuesOf<ARRAY> ...
const a = oneItem(["foo", "bar"]);
// typeof a is (string) instead of ("foo" | "bar")
在这里找到 (https://github.com/Microsoft/TypeScript/issues/20965)
如何从具有正确类型的数组中返回单个项目?
编辑:我想出了只读数组:
type ValuesOf<T extends readonly any[]>= T[number];
function oneItem<ARRAY extends readonly any[]>(arr: ARRAY): ValuesOf<ARRAY> ...
const a = oneItem(["foo", "bar"] as const);
【问题讨论】:
我错过了一些东西——为什么function oneItem<T>(arr: T[]): T
不够好?
const b = oneItem(["foo", "bar"]);
---> typeof b (string)
它应该是什么?
啊,我想我明白了!需要学习如何阅读:)
【参考方案1】:
当你说“正确”类型时,我猜你的意思是你想要编译器可以推断的最窄的类型。
这是否“适当”在旁观者的眼中;运行时代码 let x = 0
在 TypeScript 中可能有许多可能的类型,编译器只能选择其中一种,除非您自己注释类型。
例如,我可能希望它被解释为let x: number = 0
,这是默认的编译器行为,如果我打算稍后为x
分配不同的数字,这很有意义。或者我可能想要let x: 0 | 1 = 0
,因为我只会编写像x = y & 1
这样的代码,而且我知道只有那些值会出来。或者我可能想要let x: 0 = 0
,因为x
将永远是0
(尽管在这种情况下我可能会使用const
)。或者也许我想要let x: number | string = 0
,因为有时我会给它分配string
值。或者,也许我想要任何其他包含 0
作为值的疯狂类型。
如果你自己不注释,那么编译器必须推断类型,所以它做出一个假设:非readonly
和非const
值将倾向于被推断为“加宽”类型像string
、number
或boolean
,而readonly
和const
值将倾向于被推断为“窄”literal type 像"foo
"、0
或true
.
有时编译器的默认假设与开发人员的意图不符。这并不意味着编译器做错了什么。并不是说它需要let x = 0
并推断string
为x
。这只是意味着开发人员可能需要付出更多努力来传达他或她的意图。
所以,当我写["foo", "bar"]
时,编译器倾向于将其推断为string[]
。如果我想要别的东西,我可以自己注释。让我们从这个版本的函数开始:
function oneItem<T>(arr: readonly T[]): T
return arr[~~(Math.random() * arr.length)];
以下是一些输出:
let arr1 = ["foo", "bar"]; // inferred as string[]
const res1 = oneItem(arr1); // string
let arr2: Array<"foo" | "bar"> = ["foo", "bar"];
const res2 = oneItem(arr2); // "foo" | "bar"
let arr3: ["foo", "bar"] = ["foo", "bar"];
const res3 = oneItem(arr3); // "foo" | "bar"
let arr4: readonly ["foo", "bar"] = ["foo", "bar"];
const res4 = oneItem(arr4); // "foo" | "bar"
let arr5 = ["foo", "bar"] as const; // inferred as readonly ["foo", "bar"];
const res5 = oneItem(arr5);
您可以看到arr1
使用默认的string[]
推断类型,因此string
从函数中出来。对于arr2
、arr3
和arr4
,我已将数组注释为逐渐变窄的类型,并且它们都允许"foo" | "bar"
退出函数。最后一个arr5
使用const
assertion 要求编译器将类型推断为它可以推断的最窄值,对应于arr4
。
因此,您可以继续进行的一种方法是更严格地指定值的类型。
当然,在你的用例中,你有类似的东西
const resArrayLiteral = oneItem(["foo", "bar"]); // string
您希望出现"foo" | "bar"
而不是string
。为什么oneItem()
的调用者 必须要求编译器缩小数组字面量["foo", "bar"]
的类型? oneItem()
的 implementer 不能使用某种上下文类型提示来告诉编译器更窄地解释事物吗?
嗯,有点,是的。它只是不漂亮。这是一种方法:
type Narrowable = string | number | boolean | symbol | object | undefined | void | null | ;
function oneItem<T extends Narrowable>(arr: readonly T[]): T
return arr[~~(Math.random() * arr.length)];
在这里,我们将 T
和 constraint 分配给 Narrowable
,这是一系列事物的联合类型,包括 string
、number
和 boolean
。事实上,Narrowable
类型是unknown
的一种乏味版本,因为大多数东西都应该分配给它。但是Narrowable
向编译器提供了一个提示(请参阅microsoft/TypeScript#10676),如果可能的话,您希望将T
推断为文字类型。
让我们看看它是否有效:
const resArrayLiteral = oneItem(["foo", "bar"]); // "foo" | "bar"
现在看起来不错! T
被推断为"foo" | "bar"
而不是string
。
不过,正如我所说:它并不漂亮。有一个open issue, (microsoft/TypeScript#30680),表明我们可以将oneItem()
签名写成类似
function oneI<const T>(arr: readonly T[]): T; // currently invalid syntax
const
在签名中的位置,其行为类似于让调用者使用as const
。不过,不确定这个问题是否会发生。现在,我们必须跳过像Narrowable
这样的圈套。
另外,请注意上述T extends Narrowable
不会改变arr1
的行为。如果你写let arr1 = ["foo", "bar"]
,那么arr
将被推断为string[]
。一旦它是 string[]
,编译器就会忘记所有关于 "foo"
和 "bar"
的文字类型。因此oneItem(arr1)
将返回string
; T
将被推断为 string
。所以在某些时候你可能会发现自己需要自己编写窄类型,如果你想要的话。
好的,希望对您有所帮助;祝你好运!
Playground Link to code
【讨论】:
以上是关于Typescript 函数,它从具有正确类型的数组中返回一项的主要内容,如果未能解决你的问题,请参考以下文章