typescript - 打字稿如何键入字符串文字数组的子集
问题描述
我有函数createFields,它采用一个通用的 Object 类型,在本例中是 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;
解决方案
问题是它Fields<T>
不够通用。它selectable
和sortable
属性是Array<keyof T>
,它太宽以至于无法记住使用了哪个子集keyof T
。我处理这个问题的方法是createFields()
接受一个泛型类型F
,它被限制在Fields<T>
.
您这样做时面临的一个问题是编译器无法进行部分类型参数推断。您要么需要同时指定T
and F
,要么让编译器同时推断两者。编译器真的没有任何东西可以推断T
. 假设您想指定T
我这样做的方式是使用柯里化:
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"
属性F
,并查找它的number
索引类型 ( F["selectable"][number]
)... 我们接受该类型的任何数组或只读数组。我不只是使用的原因F["selectable"]
是因为如果该类型是固定顺序和长度的元组,我们不希望fields
需要相同的顺序/长度。
对于 curried 或 non-curried 情况,您需要创建一个不扩大到也不扩大到UserFields
的对象类型。这是一种方法:Fields<T>
selectable
sortable
string[]
const UserFields = {
selectable: ['id', 'username', 'age'],
sortable: ['username', 'age']
} as const; // const assertion doesn't forget about sting literals
我用一个const
断言来保持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"'
这就是你想要的,对吧?
non-curried don't-care-about-User
解决方案类似,但不会捕获"oops"
:
const alsoFields = createFields(UserFields);
const alsoBadFields =
createFields({ selectable: ["password"], sortable: ["id", "oops"] }); // no error
我猜你想使用哪一个(如果有的话)取决于你的用例。这里的要点是,您需要createFields()
对selectable
andsortable
属性中的键类型具有通用性。究竟如何做到这一点取决于你。希望有帮助;祝你好运!
推荐阅读
- c# - 一个项目中的 IdentityServer4 和 Web Api 无法通过身份验证
- token - 如何获取 IBM Watson 的 Text-to-Speech 服务的令牌?
- autohotkey - Autohotkey 和 Stream Deck - 识别按键
- node.js - Node.js 如何用 RegEx 对象回复,Hapijs
- java - 有条件地更新事务中的值
- html - XPath 查找元素
- c# - 使用@Html.PasswordFor 时我可以不将密码显示为数据表单吗
- android - 如何使用 Firebase 修复严格模式磁盘读取冲突
- shopware - 如何获取订单文章图片
- python - Pandas DataFrame:在多列条件下对数据框进行编程行拆分