英文:
Confusing syntax when creating function with generic return type
问题
我试图在 TypeScript 中更加熟悉使用泛型。
在下面的示例中,我理解了大部分的内容:
首先我创建了一个 Person
对象类型
type Person = {
name: string,
age: number,
location: string
}
然后我创建了一个接受两个泛型 T
和 K
的 getUsers
函数。
const getUsers = <T extends Person, K extends keyof T>(users: T[], key: K): T[K][] => {
users.map(user => console.log(`${user[key]}`));
}
T
扩展了我的 Person
类型,因为我希望它是一个对象,而 K
扩展了 T
的键,因为我想要从 Person
对象中获取键的索引签名。
然后有两个参数 users
,它是一个 T
类型的数组,和 key
,它将是 T
的一个键。
我困惑的地方在于这里:
T[K][]
这对我来说看起来很奇怪,因为在 TypeScript 中,如果你想将一个类型设置为多维数组,你会使用双括号 [][]
。
而这里 [K][]
对我来说看起来像是嵌套数组,能有人详细解释一下为什么语法是这样的吗?
我理解它只是在说对象的数组,但我之所以知道是因为我看了一个使用这个例子的视频,如果没有那个例子,我不会得出这个结论。
英文:
I'm trying to get more comfortable using generics in TypeScript.
In the example below I understand the majority of what's going on:
Firstly I create a Person
object type
type Person = {
name: string,
age: number,
location: string
}
I then create a getUsers
function which accepts 2 generics, T
and K
.
const getUsers = <T extends Person, K extends keyof T>(users: T[], key: K): T[K][] => {
users.map(user => console.log(`${user[key]}`));
}
T
extends my Person
type because I want this to be an object, and K
extends the keys of T
because I want the keys index signature from the Person
object.
I then have two arguments users
which is an array of T
, and key
which will be a key of T
.
My confusion is here:
T[K][]
This looks weird to me because in TypeScript if you want to set a type to be a multi-dimension array you do double brackets [][]
.
And here [K][]
this to me looks like nested array, could someone please explain in more detail why the syntax is like this?
I understand it's just saying an array of objects but I only know that because I watched a video using this example, if I saw this without that example I wouldn't have come to that conclusion.
答案1
得分: 1
你目前错误地将 T[K][]
理解为类似于 T([K][])
,实际上它是 (T[K])[]
。 TypeScript 类型系统中的方括号字符 ([
和 ]
) 有一些不完全相关的不同用法:
-
T[]
:如果你有一个类型T
,那么 "T[]
" 是Array<T>
的同义词。还有readonly T[]
,它是ReadonlyArray<T>
的同义词。 -
T[K]
:如果你有一个类似对象的类型T
和一个类似键的类型K
,那么T[K]
是索引访问类型,表示从类型T
的对象中使用类型K
的键进行索引时得到的值的类型。 -
[]
、[T]
、[T, U]
、[T, U, V]
、[T, U, V, ...W[]]
、[T, ...A, U]
、[T, U?]
、readonly [T, U]
等等:这些都是元组类型。其中一些语法涉及可选元素、只读元组和剩余元素,以及可变元组类型。特别要注意区分 空元组[]
和类似T[]
的数组,以及 单元素元组[K]
和索引访问类型T[K]
。它们看起来相似(如果忽略开放方括号前发生的事情),但它们不相关。 -
{[k]: T}
、{["x"]: T}
、{[k: string]: T}
、{[K in P]: T}
、{[T in U as K<T>]: V}
等等:这些都使用方括号对应于对象类型内部的类似键的内容。计算属性键(如{[k]: T}
和{["x"]: T}
)、索引签名(如{[k: string]: T}
)以及映射类型(如{[K in P]: T}
和{[T in U as K<T>]: V}
)之间有重要的区别。希望这些不容易与其他类型混淆,因为它们被大括号括起来。
无论如何,T[K][]
等同于 Array<T[K]>
,这意味着:这是一个数组的类型,其元素的类型是通过从类型 T
中使用类型 K
的键读取属性时获得的类型。在你的示例代码中,函数签名 {<T extends Person, K extends keyof T>(users: T[], key: K): T[K][]}
表示一个函数,该函数接受一个 users
数组,它的子类型 T
是 Person
的一种,以及一个类型为 K
的键 key
,已知它是 T
的一个键,然后返回一个值数组,该数组对应于 users
数组中元素的 key
键的属性。实际上,仅仅通过这个签名,我们可以猜测这个函数的实现是 return users.map(user => user[key])
,这是有效的:
const getUsers = <T extends Person, K extends keyof T>(
users: T[], key: K
): T[K][] => users.map(user => user[key]); // okay
英文:
You are incorrectly reading T[K][]
as something like T([K][])
when it is actually (T[K])[]
. There are a few distinct and not-completely related uses of the square bracket characters ([
and ]
) in TypeScript's type system:
-
T[]
: if you have a typeT
, then "T[]
" is a synonym forArray<T>
. There's alsoreadonly T[]
which is a synonym forReadonlyArray<T>
-
T[K]
: if you have an object-like typeT
and a key-like typeK
, thenT[K]
is the indexed access type representing the type of value you'd get when indexing into an object of typeT
with a key of typeK
. -
[]
,[T]
,[T, U]
,[T, U, V]
,[T, U, V, ...W[]]
,[T, ...A, U]
,[T, U?]
,readonly [T, U]
, etc: there are all tuple types of some form or other. Some of that syntax has to do involves optional elements, readonly tuples, and rest elements, as well as variadic tuple types. In particular you should be careful to distinguish between the empty tuple[]
and an array likeT[]
, and to distinguish between a single-element tuple[K]
and an indexed access likeT[K]
. They look similar (if you ignore what's happening before the opening bracket) but they are not related. -
{[k]: T}
,{["x"]: T}
,{[k: string]: T}
,{[K in P]: T}
,{[T in U as K<T>]: V}
, etc. these all use square brackets to correspond to keylike things inside an object type. There are important distinctions between computed property keys (like{[k]: T}
and{["x"]: T}
, index signatures (like{[k: string]: T}
), and mapped types (like{[K in P]: T}
and{[T in U as K<T>]: V}
). Hopefully these aren't as easy to confuse with the others, since they are surrounded by curly braces.
Anyway, T[K][]
is equivalent to Array<T[K]>
, which means: the type of an array whose elements are of type you get when reading a property from type T
with a key of type K
. In your example code, the call signature {<T extends Person, K extends keyof T>(users: T[], key: K): T[K][]}
means a function which accepts a users
array of some subtype T
of Person
, as well as a key key
of a type K
known to be a key of T
, and which returns an array of values corresponding to the key
-keyed property of the elements of the users
array. Indeed, just by that signature, it would be a plausible guess that the implementation of such a call signature would be return users.map(user => user[key])
, which works:
const getUsers = <T extends Person, K extends keyof T>(
users: T[], key: K
): T[K][] => users.map(user => user[key]); // okay
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论