打字稿:获取类型的属性字段名称

时间:2019-08-21 13:12:18

标签: typescript

我需要获取类型为number的所有类型的属性:

export interface ApplicationQuote {
    readonly chronoUnit: ApplicationQuote.ChronoUnitEnum;
    readonly downloadedDocs: number;
    readonly downloadedKb: number;
    readonly uploadedKb: number;
    readonly uploadedRefs: number;
}
export namespace ApplicationQuote {
    export type ChronoUnitEnum = 'HOUR' | 'DAY' | 'MONTH' | 'YEAR';
    export const ChronoUnitEnum = {
        HOUR: 'HOUR' as ChronoUnitEnum,
        DAY: 'DAY' as ChronoUnitEnum,
        MONTH: 'MONTH' as ChronoUnitEnum,
        YEAR: 'YEAR' as ChronoUnitEnum
    };
}

我需要像Array<string>这样的["downloadedDocs", "downloadedKb", "uploadedKb", "uploadedRefs"]

我尝试了以下代码:

let names = Object.getOwnPropertyNames(ApplicationQuote);
let keys = Object.keys(ApplicationQuote);

enter image description here

1 个答案:

答案 0 :(得分:2)

我将把您的ApplicationQuote接口重命名为IApplicationQuote,以使其与ApplicationQuote名称空间区分开。您会立即发现的一个主要问题是,您无法做类似Object.keys(IApplicationQuote)的事情:

Object.keys(IApplicationQuote); // error!
// 'IApplicationQuote' only refers to a type, but is being used as a value here.

接口是TypeScript类型系统的一部分,完全来自发出的JavaScript erased。接口仅在设计时(即编写程序时)存在,并且可以在编译时(发出JavaScript时)进行检查……但是它们在运行时就消失了。因此,在运行时没有名称IApplicationQuote可以处理。

因此,您将需要编写如下内容:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedKb",
  "uploadedRefs"
] as const;

as constconst assertion,允许编译器将numericProps视为字符串文字的元组,而不仅仅是string[])。


那么,从这里开始有几种方法。一种是保留您的界面并使用编译器来帮助确保您的numericProps值具有所有且只有正确的成员。首先,我们可以定义KeysMatching<T, V>,该结果的值是T中可分配给类型V的键的并集:

type KeysMatching<T, V> = NonNullable<
  { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
>;

并像这样使用它:

type NumericProps = KeysMatching<IApplicationQuote, number>;
// type NumericProps = "downloadedDocs" | "downloadedKb" | "uploadedKb" | "uploadedRefs"

NumericProps类型武装,我们可以这样写:

type MutuallyAssignable<T extends U, U extends V, V = T> = true;

type NumericPropsOkay = MutuallyAssignable<
  typeof numericProps[number],
  NumericProps
>; // okay

您可以使用MutuallyAssignable<T, U>来确保为TU传递的类型是等效的;如果不是,您将得到一个错误(因此MutuallyAssignable<string, string | "a">很好,但是MutuallyAssignable<number, number | "a">则不是)。由于NumericPropsOkay不会产生错误,因此您知道numericProps并没有犯错。

考虑如果您这样更改numericProps会发生什么:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedkb", // <-- note the typo
  "uploadedRefs"
] as const;

然后会发生这种情况:

type NumericPropsOkay = MutuallyAssignable<
  typeof numericProps[number], // error!
  NumericProps
>;
// Type '"uploadedkb"' is not assignable to type 
// '"downloadedDocs" | "downloadedKb" | "uploadedKb" | "uploadedRefs"'.

因此,您可以使用它来确保手动编写的数组和接口定义在将来不会出现歧义。


另一种进行方法是根据numericProps值定义接口。就是说,与您所要求的相反。JavaScript对TypeScript一无所知,但TypeScript对JavaScript有所了解。所以你可以这样:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedKb",
  "uploadedRefs"
] as const;

并使用它来定义此接口:

export interface IApplicationQuote
  extends Readonly<Record<typeof numericProps[number], number>> {
  readonly chronoUnit: ApplicationQuote.ChronoUnitEnum;
}

在这里,我们定义了IApplicationQuote以扩展Readonly<Record<typeof numericProps[number], number>>,这与{readonly downloadedDocs: number, readonly downloadedKb: number, ...}基本上相同。我们只需要在其中添加一个非数字属性。您可以确保它起作用:

const appQuote: IApplicationQuote = {
  chronoUnit: "DAY",
  downloadedDocs: 1,
  downloadedKb: 2,
  uploadedKb: 3,
  uploadedRefs: 4
}; // okay

您可以验证IApplicationQuote仍然是您要查找的类型:

const badAppQuote: IApplicationQuote = {
  chronoUnit: "DAY",
  downloadedDocs: 1,
  downloadedKb: 2,
  uploadedkb: 3, // error!
  uploadedRefs: 4
}; 
// 'uploadedkb' does not exist in type 'IApplicationQuote'. 
// Did you mean to write 'uploadedKb'?

看起来不错。


好的,希望能有所帮助;祝你好运!

Link to code