gpt4 book ai didi

typescript - Typescript 4.0+ 是否能够使用映射的可变参数元组类型来运行函数?

转载 作者:行者123 更新时间:2023-12-02 02:34:38 24 4
gpt4 key购买 nike

举个例子,假设我有一个简单的函数,它将可变数量的事物映射到对象数组,例如 { a: value }

const mapToMyInterface = (...things) => things.map((thing) => ({ a: thing}));

Typescript 尚无法推断此函数结果的强类型:

const mapToMyInterface = mapToInterface(1, 3, '2'); // inferred type{ a: any }[]

首先,我定义一个类型来描述映射到可观察值的数组:

type MapToMyInterface<T extends any[]> = {
[K in keyof T]: T[K] extends Array<infer U> ? { a: U } : { a: T[K] }
}

现在我更新我的函数:

const mapToMyInterface = <T extends any[]>(...things: T): MapToMyInterface<T> => things.map((thing) => ({ a: thing}));

到目前为止,Typescript 还不满意。函数的返回表达式突出显示错误“TS2322:类型 '{ a: any; }[]' 无法分配给类型 'MapToMyInterface'”

显然,参数thing需要在映射函数中显式输入。但我不知道如何表达“第 n 种类型”,而这正是我需要的。

也就是说,既不将 thing 标记为 T[number],也不执行以下操作:

const mapToMyInterface = <T extends any[], K = keyof T>(...things: T): MapToMyInterface<T> => things.map((thing: T[K]) => of(thing));

这可以在 Typescript 中工作吗?

在@jcalz的回答后进行编辑:为了后代,我想发布我的问题的原始动机,以及我从 @jcalz 的回答中得到的解决方案。

我试图包装一个 RxJs 运算符,withLatestFrom,以惰性地评估传递给它的可观察量(当您可能传递一个在某处启动持续订阅的函数的结果时很有用,就像 NgRx 中的 store.select 一样)。

我能够成功断言返回值,如下所示:

export const lazyWithLatestFrom = <T extends Observable<unknown>[], V>(o: () => [...T]) =>
concatMap(value => of(value).pipe(withLatestFrom(...o()))) as OperatorFunction<
V,
[V, ...{ [i in keyof T]: TypeOfObservable<T[i]> }]
>;

最佳答案

假设您有一个通用函数 wrap()它采用 T 类型的值并返回 {a: T} 类型的值,如您的示例所示:

function wrap<T>(x: T) {
return ({ a: x });
}

如果你只是创建一个接受数组的函数 things并调用things.map(wrap) ,您将得到一个弱类型函数,正如您所注意到的:

const mapWrapWeaklyTyped = <T extends any[]>(...things: T) => things.map(wrap);
// const mapWrapWeaklyTyped: <T extends any[]>(...things: T) => {a: any}[]

这完全忘记了进入的各个类型及其顺序,而您只是一个 {a: any} 的数组。 。这很正确,但不是很有用:

const weak = mapWrapWeaklyTyped(1, "two", new Date());
try {
weak[2].a.toUpperCase(); // no error at compile time, but breaks at runtime
} catch (e) {
console.log(e); // weak[2].prop.toUpperCase is not a function
}

该死,编译器没有发现 2指的是数组的第三元素,它是一个包装的 Date而不是包装的string 。我必须等到运行时才能看到问题。


如果你看standard TypeScript library's typing对于 Array.prototype.map() ,你就会明白为什么会发生这种情况:

interface Array<T> {
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}

当您调用things.map(wrap)时,编译器推断出单个 U类型,不幸的是这将是 {a: any} ,因为如果 things类型为T extends any[] ,所有编译器都知道 things 的元素是它们可以分配给 any .

确实没有什么好的通用输入可以给Array.prototype.map()这将处理 callbackfn 的情况参数对不同类型的输入执行不同的操作。这将需要更高种类的类型,例如 type constructors ,TypeScript 目前不直接支持(有关相关功能请求,请参阅 microsoft/TypeScript#1213)。


但是如果您的 callbackfn 有特定的泛型类型, ,(例如 (x: T) => {a: T} ),您可以使用 mapped array/tuple types 手动描述元组或数组上的特定类型转换。 .

这里是:

const mapWrapStronglyTyped = <T extends any[]>(...things: T) => things.map(wrap) as
{ [K in keyof T]: { a: T[K] } };
// const mapWrapStronglyTyped:
// <T extends any[]>(...things: T) => { [K in keyof T]: {a: T[K]; }; }

我们在这里所做的只是迭代每个(数字)索引 K T的数组,并取T[K]该索引处的元素并将其映射到 {a: T[K] } .

请注意,因为标准库的类型为 map()没有预料到这个特定的通用映射函数,您必须使用 type assertion进行类型检查。如果您只关心编译器在没有类型断言的情况下无法验证这一点,那么这确实是您在 TypeScript 中没有更高种类类型的情况下可以做的最好的事情。

您可以在与之前相同的示例中进行测试:

const strong = mapWrapStronglyTyped(1, "two", new Date());
try {
strong[2].a.toUpperCase(); // compiler error, Property 'toUpperCase' does not exist on type 'Date'
} catch (e) {
console.log(e); // strong[2].prop.toUpperCase is not a function
}
// oops, I meant to do this instead!
console.log(strong[1].a.toUpperCase()); // TWO

现在编译器捕获了错误,并告诉我 Date对象没有 toUpperCase方法。万岁!


您的映射版本,

type MapToMyInterface<T extends any[]> = {
[K in keyof T]: T[K] extends Array<infer U> ? { a: U } : { a: T[K] }
}

有点奇怪,因为你做了两次映射;两次;除非您传入数组的数组,否则没有理由检查 T[K]无论它本身是否是一个数组。 T是数组,K是它的索引。所以我想说只需返回 {a: T[K]}除非我错过了一些重要的事情。


Playground link to code

关于typescript - Typescript 4.0+ 是否能够使用映射的可变参数元组类型来运行函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64519489/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com