如何使一个实例方法只接受另一个实例方法的名称?

How to make an instance method accept only the name of another instance method?

提问人:RobertAKARobin 提问时间:11/17/2023 更新时间:11/17/2023 访问量:51

问:

我有一个具有一些属性和方法的对象。如何给它一个只接受另一个方法名称的方法?

例如,考虑此类中的方法。我们想接受另一个方法的名称。使用 将可接受的类型缩小到属性名称;我们如何进一步缩小到方法名称,而不对它们进行硬编码?commandUsercommandkeyof thisUserUser

class User {
    nameFirst: string;
    nameLast: string;

    command(commandName: keyof this) {
        this[commandName].call(this);
    }

    sink() {}

    swim() {}
}

const alice = new User();
alice.command(`swim`); // Accepts `nameFirst` | `nameLast` | `command` | `sink` | `swim`; we want it to accept only `sink` and `swim`
JavaScript 打字稿

评论

0赞 Bergi 11/17/2023
您可能应该对它们进行硬编码,例如,调用或command("command")command("constructor")
0赞 gog 11/17/2023
你有没有考虑过分解,比如 和 ?interface UserCommandsclass User implements UserCommands(commandName: keyof UserCommands)

答:

1赞 sajib 11/17/2023 #1

若要将命令方法的可接受类型缩小到仅用户方法名称而不对其进行硬编码,可以将条件类型和映射类型组合在一起。

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

class User {
  nameFirst: string;
  nameLast: string;

  command(commandName: FunctionPropertyNames<User>) {
    this[commandName].call(this);
  }

  sink() {
    console.log('Sink');
  }

  swim() {
    console.log('Swim');
  }
}

const alice = new User();
alice.command('swim'); // This is now restricted to 'sink' and 'swim'

评论

0赞 jcalz 11/17/2023
“这现在仅限于'下沉'和'游泳'” 不完全是,它也允许"command"
0赞 jcalz 11/17/2023 #2

TypeScript 没有内置的类型运算符来提取其值与某个其他类型匹配的类型的键,如 microsoft/TypeScript#48992 中的要求,但您可以自己实现一个适用于某些用例的运算符,例如:KeysMatching<T, V>TV

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

然后,从概念上讲,您将创建类型,以便它只允许零参数方法。但这将导致循环性警告(因为 的类型取决于 which 的类型取决于 which 的类型取决于 的类型 取决于 的类型 )。您可以通过使用 Omit 实用程序类型省略要允许的键来规避此问题:commandNameKeysMatching<User, ()=>void>commandNameUsercommandcommandName"command"

class User {
  nameFirst: string = "";
  nameLast: string = "";

  command(commandName: KeysMatching<Omit<User, "command">, () => void>) {
    this[commandName].call(this);
  }

  sink() { }

  swim() { }

  needsParam(x: string) { console.log(x.toUpperCase()) }
}

const alice = new User();
alice.command("swim")
alice.command("sink");
alice.command("nameFirst"); // error, not accepted
alice.command("needsParam"); // error, not accepted

Playground 代码链接

0赞 enticedcode 11/17/2023 #3

不确定您具体想做什么,但这适用于您的用例吗?

class User {
    nameFirst: string;
    nameLast: string;

    command(commandName: keyof this) {
      if (typeof this[commandName] === 'function') {
            this[commandName].call(this);
      } 
      else {
        return
      }
    }

    sink() {}

    swim() {}
}

const alice = new User();
alice.command(`swim`);