简体   繁体   English

typescript 如何键入字符串文字数组的子集

[英]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.它的selectablesortable属性是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.您要么需要同时指定TF ,要么让编译器同时推断两者。 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>并且不会扩大selectablesortablestring[] 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 readonlyFields<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()selectablesortable属性中的键类型上是通用的。 Exactly how you do that is up to you.究竟如何做到这一点取决于你。 Hope that helps;希望有帮助; good luck!祝你好运!

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM