TypeScript:选择具有定义类型的属性

And*_*aev 7 typescript

是否可以构造一个 TypeScript 类型,它只会选择那些 typeof X 的属性?

interface IA {
    a: string;
    b: number;
}

interface IB extends IA {
    c: number | string;
}

type IAStrings = PickByType<IA, string>;
// IAStrings = { a: string; }
type IBStrings = PickByType<IB, string>;
// IBStrings = { a: string; }
type IBStringsAndNumbers = PickByType<IB, string | number>;
// IBStringsAndNumbers = { a: string; b: number; c: number | string; }
Run Code Online (Sandbox Code Playgroud)

小智 58

使用 Typescript 4.1,这可以变得更短,同时还允许选择可选属性,这是其他答案不允许的:

type PickByType<T, Value> = {
  [P in keyof T as T[P] extends Value | undefined ? P : never]: T[P]
}
Run Code Online (Sandbox Code Playgroud)

作为解释这里发生的事情,因为这可能会被认为是黑魔法:

  1. P in keyof TT存储所有可能的键P
  2. 访问并获取其价值as的用途PT[P]
  3. 然后我们进入条件,检查是否T[P]匹配Value | undefinedundefined以允许可选属性)。
  4. 如果 的值T[P]匹配Value | undefined,则我们将其设置P为该类型的属性及其对应的值T[P]
  5. 设置为的类型属性never最终不会出现在结果类型中,从而显式删除与您要选择的类型不匹配的任何属性。

  • [链接](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types) 到“as”子句的文档。 (2认同)
  • 对于懒惰/聪明的人来说,这已经是令人惊叹的实用程序类型打字稿库的一部分-https://github.com/piotrwitek/utility-types#pickbyvaluet-valuetype (2认同)
  • @chrismarx 您链接的库和方法是完美的。我无法让上面的自定义解决方案起作用,因为“never”会导致冲突,但实用程序类型立即起作用。谢谢你的提示! (2认同)

hlo*_*dal 13

是的,这是可能的。我来到这个问题也是为了寻找答案,但我最终想通了。

TL; 博士

/**
 * Returns an interface stripped of all keys that don't resolve to U, defaulting 
 * to a non-strict comparison of T[key] extends U. Setting B to true performs
 * a strict type comparison of T[key] extends U & U extends T[key]
 */
type KeysOfType<T, U, B = false> = {
  [P in keyof T]: B extends true 
    ? T[P] extends U 
      ? (U extends T[P] 
        ? P 
        : never)
      : never
    : T[P] extends U 
      ? P 
      : never;
}[keyof T];

type PickByType<T, U, B = false> = Pick<T, KeysOfType<T, U, B>>;
Run Code Online (Sandbox Code Playgroud)

更长的解释性版本

class c1 {
  a: number;
  b: string;
  c: Date;
  d?: Date;
};

type t1 = keyof c1; // 'a' | 'b' | 'c' | 'd'
type t2 = Pick<c1, t1>; // { a: number; b: string; c: Date; d?: Date; }

type KeysOfType0<T, U> = {
  [P in keyof T]: T[P] extends U ? P : never;
};
type t3 = KeysOfType0<c1, Date>; // { a: never; b: never; c: "c"; d?: "d"; }

// Based on https://github.com/microsoft/TypeScript/issues/16350#issuecomment-397374468
type KeysOfType<T, U> = {
  [P in keyof T]: T[P] extends U ? P : never;
}[keyof T];

type t4 = KeysOfType<c1, Date>; // "c" | "d"

type t5 = Pick<c1, t4>; // { c: Date; d?: Date; }

type PickByType<T, U> = Pick<T, KeysOfType<T, U>>;

type t6 = PickByType<c1, Date>; // { c: Date; d?: Date; }
Run Code Online (Sandbox Code Playgroud)

因此,这PickByType正是您在评论中获得的结果。


编辑

如果您需要一个严格类型的实用程序,您需要验证扩展是双向的。下面是原始 KeysOfType 实用程序可能返回意外结果的一种情况和两种解决方案的示例。

打字稿操场上试一试。

type KeysOfType<T, U> = {
  [P in keyof T]: T[P] extends U ? P : never;
}[keyof T];

type PickByType<T, U> = Pick<T, KeysOfType<T, U>>;

type KeysOfTypeStrict<T, U> = {
    [P in keyof T]: T[P] extends U ? (U extends T[P] ? P : never) : never;
}[keyof T];
type PickByTypeStrict<T, U> = Pick<T, KeysOfTypeStrict<T, U>>;

/**
 * Returns an interface stripped of all keys that don't resolve to U, defaulting 
 * to a non-strict comparison of T[key] extends U. Setting B to true performs
 * a strict type comparison of T[key] extends U & U extends T[key]
 */
type KeysOfTypeBest<T, U, B = false> = {
  [P in keyof T]: B extends true 
    ? T[P] extends U 
      ? (U extends T[P] 
        ? P 
        : never)
      : never
    : T[P] extends U 
      ? P 
      : never;
}[keyof T];

type PickByTypeBest<T, U, B = false> = Pick<T, KeysOfTypeBest<T, U, B>>;


interface thing {
  foo: () => string;
  bar:  (resourceName: string) => string;
  test: string;
}

type origBar = PickByType<thing, thing['bar']>;
let origBar: Partial<origBar> = {};
origBar.bar; // success: true positive
origBar.foo; // success: false positive, I wasn't expecting this property to be allowed.
origBar.test // error: true negative

type origFoo = PickByType<thing, thing['foo']>;
let origFoo: Partial<origFoo> = {};
origFoo.bar; // error: true negative
origFoo.foo; // success: true positive
origFoo.test // error: true negative

type strictBar = PickByTypeStrict<thing, thing['bar']>;
let strictBar: Partial<strictBar> = {};
strictBar.bar; // success: true positive
strictBar.foo; // error: true negative
strictBar.test // error: true negative

type strictFoo = PickByTypeStrict<thing, thing['foo']>;
let strictFoo: Partial<strictFoo> = {};
strictFoo.bar; // error: true negative
strictFoo.foo; // sucess: true positive
strictFoo.test // error: true negative

type bestBarNonStrict = PickByTypeBest<thing, thing['bar']>;
let bestBarNonStrict: Partial<bestBarNonStrict> = {};
bestBarNonStrict.bar; // success: true positive
bestBarNonStrict.foo; // success: true positive, I do want to keep properties with values similar to bar
bestBarNonStrict.test // error: true negative

type bestBarStrict = PickByTypeBest<thing, thing['bar'], true>;
let bestBarStrict: Partial<bestBarStrict> = {};
bestBarStrict.bar; // success: true negative
bestBarStrict.foo; // error: true negative, I do NOT want to keep properties with values similar to bar
bestBarStrict.test // error: true negative
Run Code Online (Sandbox Code Playgroud)