是否可以[string, date, number]从像这样的接口生成元组类型{a: string, b: date, c: number}?
我正在尝试向函数添加类型,您可以在其中按顺序传递对象或对象属性的值。(不要@我,代码不是我写的。)
// This is valid
bookRepo.add({
title: 'WTF',
authors: ['Herb Caudill', 'Ryan Cavanaugh'],
date: new Date('2019-04-04'),
pages: 123,
})
// This is also valid
bookRepo.add([
'WTF', // title
['Herb Caudill', 'Ryan Cavanaugh'], // authors
new Date('2019-04-04'), // date
123, // pages
])
Run Code Online (Sandbox Code Playgroud)
所以我想象的是一种生成包含接口属性类型的元组的方法:
interface Book {
title: string
authors: string | string[]
date: Date
pages: number
}
type BookTypesTuple = TupleFromInterface<T>
// BookTypesTuple = [
// string,
// string | string[],
// Date,
// number
// ]
Run Code Online (Sandbox Code Playgroud)
所以我可以做这样的事情:
class Repo<T> {
// ...
add(item: T): UUID
add(TupleFromInterface<T>): UUID
}
Run Code Online (Sandbox Code Playgroud)
编辑该类确实有一个数组属性,用于定义字段的规范顺序。像这样的东西:
const bookRepo = new Repo<Book>(['title', 'authors', 'date', 'pages'])
Run Code Online (Sandbox Code Playgroud)
不过,我正在为通用 Repo 编写类型定义,而不是为特定实现编写类型定义。所以类型定义事先不知道该列表将包含什么。
如果Repo构造函数采用属性名称的元组,则该元组类型需要在类型中编码,Repo以便键入工作。像这样的东西:
declare class Repo<T, K extends Array<keyof T>> { }
Run Code Online (Sandbox Code Playgroud)
在这种情况下,K是 的键数组T,并且add()可以用Tand构建签名K,如下所示:
type Lookup<T, K> = K extends keyof T ? T[K] : never;
type TupleFromInterface<T, K extends Array<keyof T>> = { [I in keyof K]: Lookup<T, K[I]> }
declare class Repo<T, K extends Array<keyof T>> {
add(item: T | TupleFromInterface<T, K>): UUID;
}
Run Code Online (Sandbox Code Playgroud)
您可以验证其TupleFromInterface行为是否符合您的要求:
declare const bookRepo: Repo<Book, ["title", "authors", "date", "pages"]>;
bookRepo.add({ pages: 1, authors: "nobody", date: new Date(), title: "Pamphlet" }); // okay
bookRepo.add(["Pamplet", "nobody", new Date(), 1]); // okay
Run Code Online (Sandbox Code Playgroud)
为了完整(并展示一些毛茸茸的问题),我们应该展示构造函数的类型:
declare class Repo<T extends Record<K[number], any>, K extends Array<keyof T> | []> {
constructor(keyOrder: K & (keyof T extends K[number] ? K : Exclude<keyof T, K[number]>[]));
add(item: T | TupleFromInterface<T, K>): UUID;
}
Run Code Online (Sandbox Code Playgroud)
那里有很多事情要做。首先,T被限制为 ,Record<K[number], any>以便T可以仅从 推断出的粗略值K。然后,约束对K经由联合加宽与空的元组[],其用作一个提示适合编译器执行偏爱元组类型K,而不是仅仅数组类型。然后,构造函数参数被键入为K与条件类型的交集,以确保K使用所有键,T而不仅仅是其中一些键。并非所有这些都是必需的,但它有助于捕获一些错误。
剩下的一个大问题是Repo<T, K>需要两个类型参数,您希望手动指定,T同时K从传递给构造函数的值中推断出来。不幸的是,打字稿仍然缺少部分类型参数推断,所以它要么尝试推断既 T和K,或需要手动指定既T和K,或者我们要聪明。
如果让编译器同时推断T和K,它会推断出比 更宽的内容Book:
// whoops, T is inferred is {title: any, date: any, pages: any, authors: any}
const bookRepoOops = new Repo(["title", "authors", "date", "pages"]);
Run Code Online (Sandbox Code Playgroud)
正如我所说,您不能只指定一个参数:
// error, need 2 type arguments
const bookRepoError = new Repo<Book>(["title", "authors", "date", "pages"]);
Run Code Online (Sandbox Code Playgroud)
您可以同时指定两者,但这是多余的,因为您仍然必须指定参数值:
// okay, but tuple type has to be spelled out
const bookRepoManual = new Repo<Book, ["title", "authors", "date", "pages"]>(
["title", "authors", "date", "pages"]
);
Run Code Online (Sandbox Code Playgroud)
一种规避方法是使用柯里化将构造函数拆分为两个函数;一个调用T,另一个调用K:
// make a curried helper function to manually specify T and then infer K
const RepoMakerCurried = <T>() =>
<K extends Array<keyof T> | []>(
k: K & (keyof T extends K[number] ? K : Exclude<keyof T, K[number]>[])
) => new Repo<T, K>(k);
const bookRepoCurried = RepoMakerCurried<Book>()(["title", "authors", "date", "pages"]);
Run Code Online (Sandbox Code Playgroud)
等效地,您可以创建一个辅助函数,该函数接受一个类型T为完全忽略但用于推断T和的虚拟参数K:
// make a helper function with a dummy parameter of type T so both T and K are inferred
const RepoMakerDummy =
<T, K extends Array<keyof T> | []>(
t: T, k: K & (keyof T extends K[number] ? K : Exclude<keyof T, K[number]>[])
) => new Repo<T, K>(k);
// null! as Book is null at runtime but Book at compile time
const bookRepoDummy = RepoMakerDummy(null! as Book, ["title", "authors", "date", "pages"]);
Run Code Online (Sandbox Code Playgroud)
您可以使用后三个解决方案中的任何一个bookRepoManual,bookRepoCurried, 对bookRepoDummy您的困扰最少。或者您可以放弃Repo跟踪接受元组的add().
无论如何,希望有所帮助;祝你好运!
| 归档时间: |
|
| 查看次数: |
2457 次 |
| 最近记录: |