我有一个功能, createFields ,它带有一个通用对象类型,在这种情况下是User,带有一组键。我希望这些键的子集类型可以通过字符串文字数组来推断,例如Fields中的 selectable 属性。我可以用打字稿吗?如果是,我如何实现?谢谢。
Here is the code
interface User {
id: number;
username: string;
password: string;
age: number;
}
interface Fields<T extends object = {}> {
selectable: Array<keyof T>;
sortable: Array<keyof T>;
}
function createFields<T extends object = {}>({ selectable, sortable }: Fields<T>) {
return {
// How can I get type of selectable to be ("id" | "username" | "age")[]
select(fields: typeof selectable): string {
return fields.join(',')
}
}
}
const UserFields: Fields<User> = {
selectable: ['id', 'username', 'age'],
sortable: ['username', 'age']
}
const fields = createFields(UserFields)
// actual
// => fields.select = (fields: ("id" | "username" | "password" | "age")[]): string;
// expected
// => => fields.select = (fields: ("id" | "username" | "age")[]): string;
答案 0 :(得分:1)
问题在于Fields<T>
不够通用。它的selectable
和sortable
属性是Array<keyof T>
,太宽了以至于无法记住使用了keyof T
的哪个子集。我要处理的方法是使createFields()
接受从constrained到F
的通用类型Fields<T>
。
执行此操作时面临的一个问题是编译器无法执行partial type argument inference。您需要同时指定T
和F
,或者让编译器同时推断两者。编译器确实无法推断出T
。假设您要指定T
的方式是使用currying:
const createFieldsFor =
<T extends object>() => // restrict to particular object type if we want
<F extends Fields<T>>({ selectable, sortable }: F) => ({
select(fields: readonly F["selectable"][number][]): string {
return fields.join(',')
}
})
如果您不在乎特定的T
,则可以完全不指定它:
const createFields = <F extends Fields<any>>({ selectable, sortable }: F) => ({
select(fields: readonly F["selectable"][number][]): string {
return fields.join(',')
}
})
请注意,fields
的参数为readonly F["selectable"][number][]
,表示:查找"selectable"
的{{1}}属性,并查找其F
索引类型( number
)...,我们接受该类型的任何数组或只读数组。我不仅仅使用F["selectable"][number]
的原因是,如果该类型是具有固定顺序和长度的元组,我们不希望F["selectable"]
要求相同的顺序/长度。
对于咖喱或非咖喱情况,您需要将fields
设置为不会扩展为UserFields
且不会扩展Fields<T>
和{{1}的对象类型}至selectable
。这是一种实现方法:
sortable
我使用const
assertion来缩小string[]
的范围。这是使const UserFields = {
selectable: ['id', 'username', 'age'],
sortable: ['username', 'age']
} as const; // const assertion doesn't forget about sting literals
不接受的数组UserFields
的副作用,因此我们可以更改readonly
以允许常规和只读数组:
Fields<T>
或者您也可以用其他方法制作Fields<T>
。关键是您不能将其注释为interface Fields<T extends object> {
// readonly arrays are more general than arrays, not more specific
selectable: ReadonlyArray<keyof T>;
sortable: ReadonlyArray<keyof T>;
}
,否则它将忘记您关心的所有内容。
咖喱溶液的用法如下:
UserFields
请注意Fields<User>
中如何发生错误,因为const createUserFields = createFieldsFor<User>();
const fields = createUserFields(UserFields); // okay
const badFields =
createUserFields({ selectable: ["password"], sortable: ["id", "oops"] }); // error!
// "oops" is not assignable to keyof User ------------------> ~~~~~~
会抱怨非badFields
属性。
让我们确保出现的createUserFields()
可以按预期工作:
keyof User
这就是您想要的,对吧?
非咖喱的不在乎fields
解决方案与之类似,但是它无法捕获fields.select(["age", "username"]); // okay
fields.select(["age", "password", "username"]); // error!
// -----------------> ~~~~~~~~~~
// Type '"password"' is not assignable to type '"id" | "username" | "age"'
:
User
我想您要使用哪个(如果有)取决于您的用例。这里的重点是您需要"oops"
在const alsoFields = createFields(UserFields);
const alsoBadFields =
createFields({ selectable: ["password"], sortable: ["id", "oops"] }); // no error
和createFields()
属性中的键类型上是通用的。确切的操作方式取决于您。希望能有所帮助;祝你好运!