繁体   English   中英

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

[英]typescript how to type the subset of a string literal array

我有 function, createFields ,采用通用 Object 类型,在本例中为用户,带有一组键。 我希望这些键的子集的类型可以用字符串文字数组来推断,例如 Fields 中的selectable属性。 我可以在 typescript 中做到吗? 如果是,我该如何实现? 谢谢你。

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>不够通用。 它的selectablesortable属性是Array<keyof T> ,它太宽以至于无法记住使用了keyof T哪个子集。 我处理这个问题的方法是让createFields()接受一个泛型类型F ,它被限制Fields<T>

您这样做时面临的一个问题是编译器无法进行部分类型参数推断 您要么需要同时指定TF ,要么让编译器同时推断两者。 编译器确实没有任何东西可以推断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][] ,意思是:查找F"selectable"属性,并查找它的number索引类型( F["selectable"][number] ) ...并且我们接受该类型的任何数组或只读数组。 我不只使用F["selectable"]的原因是,如果该类型是固定顺序和长度的元组,我们不希望fields需要相同的顺序/长度。


对于咖喱或非咖喱的情况,您需要将UserFields设为 object 类型,该类型不会扩大到Fields<T>并且不会扩大selectablesortablestring[] 这是一种方法:

const UserFields = {
    selectable: ['id', 'username', 'age'],
    sortable: ['username', 'age']
} as const; // const assertion doesn't forget about sting literals

我使用了一个const断言来保持UserFields的范围很窄。 这是使 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>;
}

或者,您可以改用其他方式制作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()selectablesortable属性中的键类型上是通用的。 究竟如何做到这一点取决于你。 希望有帮助; 祝你好运!

Playground 代码链接

暂无
暂无

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

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