创建具有通用返回类型的函数时的语法混乱

huangapple go评论67阅读模式
英文:

Confusing syntax when creating function with generic return type

问题

我试图在 TypeScript 中更加熟悉使用泛型。

在下面的示例中,我理解了大部分的内容:

首先我创建了一个 Person 对象类型

type Person = {
  name: string,
  age: number,
  location: string
}

然后我创建了一个接受两个泛型 TKgetUsers 函数。

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 数组,它的子类型 TPerson 的一种,以及一个类型为 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

在Playground上查看代码

英文:

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 type T, then "T[]" is a synonym for Array&lt;T&gt;. There's also readonly T[] which is a synonym for ReadonlyArray&lt;T&gt;

  • T[K]: if you have an object-like type T and a key-like type K, then T[K] is the indexed access type representing the type of value you'd get when indexing into an object of type T with a key of type K.

  • [], [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 like T[], and to distinguish between a single-element tuple [K] and an indexed access like T[K]. They look similar (if you ignore what's happening before the opening bracket) but they are not related.

  • {[k]: T}, {[&quot;x&quot;]: T}, {[k: string]: T}, {[K in P]: T}, {[T in U as K&lt;T&gt;]: 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 {[&quot;x&quot;]: T}, index signatures (like {[k: string]: T}), and mapped types (like {[K in P]: T} and {[T in U as K&lt;T&gt;]: 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&lt;T[K]&gt;, 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 {&lt;T extends Person, K extends keyof T&gt;(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 =&gt; user[key]), which works:

const getUsers = &lt;T extends Person, K extends keyof T&gt;(
    users: T[], key: K
): T[K][] =&gt; users.map(user =&gt; user[key]); // okay

Playground link to code

huangapple
  • 本文由 发表于 2023年6月19日 02:47:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76502075.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定