TypeScript:创建类型,根据值类型删除 interface/class 的属性

TypeScript: Create type that removes properties of an interface/class based on their value-types

想象一下 class:

class Person {
  name = "Ann";
  sleep() { /*...*/ }
  eat() { /*...*/ }
}

现在我想创建一个只提取方法的类型:

// should work:
const personMethods: MethodsOf<Person> = { sleep() {}, eat() {} };
const names: keyof MethodsOf<Person>[] = ['sleep', 'eat'];
// should fail:
const personMethods: MethodsOf<Person> = { notAPersonMethod() {} };
const names: keyof MethodsOf<Person>[] = ['not', 'existing', 'methods'];

我在打字稿文档中找不到任何内容,到目前为止我的尝试都失败了。我试过了:

type MethodKeys<
  Type extends object,
  Key extends keyof Type,
> = keyof Record<Type[Key] extends AnyFn ? Key : never, any>;

type Methods<Type extends object> = {
  [Property in MethodKeys<Type, keyof Type>]: Type[Property];
};

const test: MethodKeys<Person> = 'name'; // works, but shouldn't :(
const test2: Partial<Methods<Person>> = { name: 'Ann' }, // works too :/

感谢您的帮助!

您所需要的只是一个类型,它可以返回给定 class/interface 的方法名称。您可以使用 mapped type 创建一个对象类型,其中每个值代表每个方法名称。然后,您可以索引该对象类型以获取其值类型 - 这将产生所有方法名称的联合。

type MethodKeys<T> = {
    [K in keyof T]: T[K] extends (...x: any) => any ? K : never;
}[keyof T]

现在,您可以使用 Pick 从 class/interface 中选择那些方法键。

type MethodsOf<T> = Pick<T, MethodKeys<T>>;

并且您给定的测试用例工作正常-

// works
const personMethods: MethodsOf<Person> = { sleep() {}, eat() {} };
const names: Array<keyof MethodsOf<Person>> = ['sleep', 'eat'];

const personMethods1: MethodsOf<Person> = { notAPersonMethod() {} };
// ^ Type '{ notAPersonMethod(): void; }' is not assignable to type 'MethodsOf<Person>'.
const names1: Array<keyof MethodsOf<Person>> = ['not', 'existing', 'methods'];
// ^ Type '"not"' is not assignable to type 'MethodKeys<Person>'.
// ...and more

playground 上查看。

除了@Chase 的解决方案之外,您还可以使用distributive conditional types 来过滤掉具有函数值的键:

type MethodHelper<T, K extends keyof T> = K extends any
    ? T[K] extends Function ? K
    : never : never;

然后用它来实现 MethodKeysMethods:

type MethodKeys<T> = MethodHelper<T, keyof T>;
type Methods<T> = {
    [K in MethodKeys<T>]: T[K]
};


const test: MethodKeys<Person> = 'name'; // doesn't work
const test2: MethodKeys<Person> = 'sleep'; // does work
const test3: Partial<Methods<Person>> = { name: 'Ann' }; // doesn't work
const test4: Partial<Methods<Person>> = { sleep() {} }; // does work

Playground link