[英]typescript how to type the subset of a string literal array
I have function, createFields , taking a generic Object type, in this case, User, with a set of keys.我有 function, createFields ,采用通用 Object 类型,在本例中为用户,带有一组键。 I want the type the subset of these keys, to be inferred with a string literal array, like selectable property in Fields.
我希望这些键的子集的类型可以用字符串文字数组来推断,例如 Fields 中的selectable属性。 Can I do it in typescript?
我可以在 typescript 中做到吗? If yes, how can i achieve it?
如果是,我该如何实现? Thank you.
谢谢你。
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;
The problem is that Fields<T>
isn't sufficiently generic.问题是
Fields<T>
不够通用。 Its selectable
and sortable
properties are Array<keyof T>
, which is too wide to remember which subset of keyof T
is used.它的
selectable
和sortable
属性是Array<keyof T>
,它太宽以至于无法记住使用了keyof T
的哪个子集。 The way I'd deal with this is to make createFields()
accept a generic type F
that is constrained to Fields<T>
.我处理这个问题的方法是让
createFields()
接受一个泛型类型F
,它被限制为Fields<T>
。
One issue you face doing this is that the compiler can't do partial type argument inference .您这样做时面临的一个问题是编译器无法进行部分类型参数推断。 You either need to specify both
T
and F
, or let the compiler infer both.您要么需要同时指定
T
和F
,要么让编译器同时推断两者。 And there really isn't anything for the compiler to infer for T
.编译器确实没有任何东西可以推断
T
。 Assuming you want to specify T
the way I'd do this is to use currying:假设您想指定
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(',')
}
})
If you don't care about the particular T
, then you can just leave it unspecified entirely:如果您不关心特定的
T
,那么您可以完全不指定它:
const createFields = <F extends Fields<any>>({ selectable, sortable }: F) => ({
select(fields: readonly F["selectable"][number][]): string {
return fields.join(',')
}
})
Notice that the argument to fields
is readonly F["selectable"][number][]
, meaning: look up the "selectable"
property of F
, and look up its number
index type ( F["selectable"][number]
)... and we are accepting any array or readonly array of that type.注意
fields
的参数是readonly F["selectable"][number][]
,意思是:查找F
的"selectable"
属性,并查找它的number
索引类型( F["selectable"][number]
) ...并且我们接受该类型的任何数组或只读数组。 The reason I don't just use F["selectable"]
is because if that type is a tuple of fixed order and length, we don't want fields
to require the same order/length.我不只使用
F["selectable"]
的原因是,如果该类型是固定顺序和长度的元组,我们不希望fields
需要相同的顺序/长度。
For either the curried or non-curried case you need to make UserFields
an object type that doesn't widen to Fields<T>
and doesn't widen selectable
and sortable
to string[]
.对于咖喱或非咖喱的情况,您需要将
UserFields
设为 object 类型,该类型不会扩大到Fields<T>
并且不会扩大selectable
和sortable
到string[]
。 Here's one way to do it:这是一种方法:
const UserFields = {
selectable: ['id', 'username', 'age'],
sortable: ['username', 'age']
} as const; // const assertion doesn't forget about sting literals
I used a const
assertion to keep UserFields
narrow.我使用了一个
const
断言来保持UserFields
的范围很窄。 This as the side effect of making the arrays readonly
which Fields<T>
doesn't accept, so we can change Fields<T>
to allow both regular and read-only arrays:这是使 arrays
readonly
而Fields<T>
不接受的副作用,因此我们可以更改Fields<T>
以允许常规和只读 arrays:
interface Fields<T extends object> {
// readonly arrays are more general than arrays, not more specific
selectable: ReadonlyArray<keyof T>;
sortable: ReadonlyArray<keyof T>;
}
Or you can make UserFields
some other way instead.或者,您可以改用其他方式制作
UserFields
。 The point is you can't annotate it as Fields<User>
or it will forget everything you care about.关键是您不能将其注释为
Fields<User>
否则它将忘记您关心的所有内容。
The curried solution is used like this:咖喱解决方案是这样使用的:
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 ------------------> ~~~~~~
Notice how there's an error in badFields
because createUserFields()
will complain about non- keyof User
properties.请注意
badFields
中的错误,因为createUserFields()
会抱怨非keyof User
属性。
Let's make sure the fields
that comes out works as expected:让我们确保出现的
fields
按预期工作:
fields.select(["age", "username"]); // okay
fields.select(["age", "password", "username"]); // error!
// -----------------> ~~~~~~~~~~
// Type '"password"' is not assignable to type '"id" | "username" | "age"'
That's what you wanted, right?这就是你想要的,对吧?
The non-curried don't-care-about- User
solution is similar, but it won't catch "oops"
: non-curried don't-care-about-
User
解决方案类似,但它不会捕获"oops"
:
const alsoFields = createFields(UserFields);
const alsoBadFields =
createFields({ selectable: ["password"], sortable: ["id", "oops"] }); // no error
I guess which one (if any) you want to use depends on your use case.我猜你想使用哪一个(如果有的话)取决于你的用例。 The main point here is that you need
createFields()
to be generic in the type of the keys in the selectable
and sortable
properties.这里的要点是,您需要
createFields()
在selectable
和sortable
属性中的键类型上是通用的。 Exactly how you do that is up to you.究竟如何做到这一点取决于你。 Hope that helps;
希望有帮助; good luck!
祝你好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.