如何从 TypeScript 中的可变通用元组创建新元组?
How do I create a new tuple from a variadic generic tuple in TypeScript?
给定:
class Foo {}
class Bar {}
interface QueryResult<T> {
data: T;
}
function creatQueryResult<T>(data: T): QueryResult<T> {
return {data};
}
function tuple<T extends any[]>(...args: T): T {
return args;
}
我想使用接受 QueryResult[]
或 QueryResult
元组的推断类型和一个选择 data
并调用回调的工厂回调来创建和键入一个函数,如:
function createCompoundResult<T>(
queryResults: T,
callback: (queryResultDatas: ???<T>) => any
) {
const datas = queryResults.map((queryResult) => queryResult.data);
return callback(datas);
}
注意上面代码中的???
。
用法:
const fooQueryResult = creatQueryResult(new Foo());
const barQueryResult = creatQueryResult(new Bar());
// Maybe using tuples are wrong?
const queryResults = tuple(fooQueryResult, barQueryResult);
createCompoundResult(
queryResults,
(datas) => {
const [foo, bar] = datas;
// foo should be inferred as Foo here and bar as Bar
}
);
也许元组是错误的方法?你会怎么解决?
我是一个 TypeScript 新手,我很难理解像 keyof
、extends keyof
、{ [K in keyof T]: { a: T[K] } }
这样的东西,所以如果你的解决方案包括这样的神秘魔法,请像我5岁一样给我解释一下。
我对 createCompoundResult()
的建议是:
function createCompoundResult<T extends any[]>(
queryResults: readonly [...{ [I in keyof T]: QueryResult<T[I]> }],
callback: (queryResultDatas: readonly [...T]) => any
) {
const datas = queryResults.map((queryResult) => queryResult.data) as T;
return callback(datas);
}
函数是 generic in T
, corresponding to the tuple of arguments to callback
. In order to describe the type of queryResults
in terms of the array/tuple type T
, we want to map it to another array/tuple type,其中对于 T
的每个数字索引 I
,元素类型 T[I]
映射到 QueryResult<T[I]>
。所以如果 T
是 [string, number]
,那么我们希望 queryResults
的类型是 [QueryResult<string>, QueryResult<number>]
.
您可以通过 mapped type. It looks like { [I in keyof T]: QueryResult<T[I]> }
. For array-like generic types T
, mapped types like [I in keyof T]
only iterate over the numeric-like keys I
(and skip all the other array keys like "push"
and "length"
). So you can imagine { [I in keyof T]: QueryResult<T[I]> }
acting on a T
of [string, boolean]
operating on I
being "0"
and then "1"
, and T["0"]
is string
and T["1"]
is boolean
, so you get {0: QueryResult<string>, 1: QueryResults<boolean>}
, which is magically interpreted as a new tuple type [QueryResult<string>, QueryResult<boolean>]
.
这是主要的解释,尽管还有一些突出的事情要提。
首先是编译器不知道the array map()
method will turn a tuple into a tuple, and it definitely doesn't know that queryResult => queryResult.data
will turn a tuple of type { [I in keyof T]: QueryResult<T[I]> }
into a tuple of type T
. (.) It sees the output type of your queryResults.map(...)
line as T[number][]
, meaning: some array of the element types of T
. It has lost length and order information. So we have to use a type assertion告诉编译器queryResults.map(...)
的输出是T
类型,这样datas
就可以传递给 callback
.
接下来,我在 readonly [...AAA]
中包装了数组类型 AAA
的几个地方。这使用 variadic tuple type 语法向编译器提示我们希望它推断元组类型而不是数组类型。如果你不使用它,那么 [fooQueryResult, barQueryResult]
之类的东西往往会被推断为数组类型 Array<QueryResult<Foo> | QueryResult<Bar>>
而不是所需的元组类型 [QueryResult<Foo>, QueryResult<Bar>]
。使用此语法使我们无需使用 tuple()
辅助函数,至少如果您直接传递数组文字。
无论如何,让我们确保它有效:
class Foo { x = 1 }
class Bar { y = 2 }
createCompoundResult(
[fooQueryResult, barQueryResult],
(datas) => {
const [foo, bar] = datas;
foo.x
bar.y
}
);
看起来不错。我给了 Foo
和 Bar
一些结构(即使是示例代码也总是 recommended 这样做)并且果然,编译器理解 datas
是一个元组,它的第一个元素是 Foo
,其第二个元素是 Bar
.
给定:
class Foo {}
class Bar {}
interface QueryResult<T> {
data: T;
}
function creatQueryResult<T>(data: T): QueryResult<T> {
return {data};
}
function tuple<T extends any[]>(...args: T): T {
return args;
}
我想使用接受 QueryResult[]
或 QueryResult
元组的推断类型和一个选择 data
并调用回调的工厂回调来创建和键入一个函数,如:
function createCompoundResult<T>(
queryResults: T,
callback: (queryResultDatas: ???<T>) => any
) {
const datas = queryResults.map((queryResult) => queryResult.data);
return callback(datas);
}
注意上面代码中的???
。
用法:
const fooQueryResult = creatQueryResult(new Foo());
const barQueryResult = creatQueryResult(new Bar());
// Maybe using tuples are wrong?
const queryResults = tuple(fooQueryResult, barQueryResult);
createCompoundResult(
queryResults,
(datas) => {
const [foo, bar] = datas;
// foo should be inferred as Foo here and bar as Bar
}
);
也许元组是错误的方法?你会怎么解决?
我是一个 TypeScript 新手,我很难理解像 keyof
、extends keyof
、{ [K in keyof T]: { a: T[K] } }
这样的东西,所以如果你的解决方案包括这样的神秘魔法,请像我5岁一样给我解释一下。
我对 createCompoundResult()
的建议是:
function createCompoundResult<T extends any[]>(
queryResults: readonly [...{ [I in keyof T]: QueryResult<T[I]> }],
callback: (queryResultDatas: readonly [...T]) => any
) {
const datas = queryResults.map((queryResult) => queryResult.data) as T;
return callback(datas);
}
函数是 generic in T
, corresponding to the tuple of arguments to callback
. In order to describe the type of queryResults
in terms of the array/tuple type T
, we want to map it to another array/tuple type,其中对于 T
的每个数字索引 I
,元素类型 T[I]
映射到 QueryResult<T[I]>
。所以如果 T
是 [string, number]
,那么我们希望 queryResults
的类型是 [QueryResult<string>, QueryResult<number>]
.
您可以通过 mapped type. It looks like { [I in keyof T]: QueryResult<T[I]> }
. For array-like generic types T
, mapped types like [I in keyof T]
only iterate over the numeric-like keys I
(and skip all the other array keys like "push"
and "length"
). So you can imagine { [I in keyof T]: QueryResult<T[I]> }
acting on a T
of [string, boolean]
operating on I
being "0"
and then "1"
, and T["0"]
is string
and T["1"]
is boolean
, so you get {0: QueryResult<string>, 1: QueryResults<boolean>}
, which is magically interpreted as a new tuple type [QueryResult<string>, QueryResult<boolean>]
.
这是主要的解释,尽管还有一些突出的事情要提。
首先是编译器不知道the array map()
method will turn a tuple into a tuple, and it definitely doesn't know that queryResult => queryResult.data
will turn a tuple of type { [I in keyof T]: QueryResult<T[I]> }
into a tuple of type T
. (queryResults.map(...)
line as T[number][]
, meaning: some array of the element types of T
. It has lost length and order information. So we have to use a type assertion告诉编译器queryResults.map(...)
的输出是T
类型,这样datas
就可以传递给 callback
.
接下来,我在 readonly [...AAA]
中包装了数组类型 AAA
的几个地方。这使用 variadic tuple type 语法向编译器提示我们希望它推断元组类型而不是数组类型。如果你不使用它,那么 [fooQueryResult, barQueryResult]
之类的东西往往会被推断为数组类型 Array<QueryResult<Foo> | QueryResult<Bar>>
而不是所需的元组类型 [QueryResult<Foo>, QueryResult<Bar>]
。使用此语法使我们无需使用 tuple()
辅助函数,至少如果您直接传递数组文字。
无论如何,让我们确保它有效:
class Foo { x = 1 }
class Bar { y = 2 }
createCompoundResult(
[fooQueryResult, barQueryResult],
(datas) => {
const [foo, bar] = datas;
foo.x
bar.y
}
);
看起来不错。我给了 Foo
和 Bar
一些结构(即使是示例代码也总是 recommended 这样做)并且果然,编译器理解 datas
是一个元组,它的第一个元素是 Foo
,其第二个元素是 Bar
.