【问题标题】:Get object type based on it's property values根据其属性值获取对象类型
【发布时间】:2021-03-18 14:11:56
【问题描述】:

我试图通过在聊天命令中引入静态约束来减少代码量和错误。 命令用名称和参数描述。这是我得到的:

type ArgumentTypeName = 'string' | 'number' | 'user';
type CommandArguments<T> = { [k in keyof T]: ArgumentTypeName };
type CommandHandler<T> = (args: T) => void;

type Command<T> = {
  name: string;
  arguments: CommandArguments<T>;
  handler: CommandHandler<T>;
}

我不知道如何从Command&lt;T&gt; 的arguments 中获取T 类型,因此它可以正确影响handler。 到目前为止,我得到了一个具有正确键的类型,但所有值都是unknown。像这样:

arguments = { arg1: 'string' }
// handler: (args: { arg1: unknown }) => void

我也试过约束arguments,想着如果不指定T,就会从arguments推断出来,但是没用:

type ArgumentName<T> = 
  T extends string ? 'string' :
  T extends number ? 'number' :
  T extends User ? 'user' :
  never;

type CommandArguments<T> = { [k in keyof T]: ArgumentName<T[k]> };
type CommandHandler<T> = (args: T) => void;

type Command<T> = {
  name: string;
  arguments: CommandArguments<T>;
  handler: CommandHandler<T>;
}

我怎样才能根据T 的属性CommandArguments&lt;T&gt; 向后推断CommandArguments&lt;T&gt;?
所以我得到CommandArguments&lt;T&gt; → T → CommandHandler&lt;T&gt;?
还是有我没有想到的不同方法?

编辑: 使用这个的代码示例:

/*
In another file:
  const commands: Command<any>[] = [];
  export function createCommand<T>(cmd: Command<T>) {
    commands.push(cmd);
  }
*/

createCommand({
  name: 'add',
  arguments: {
    a: 'number',
    b: 'number'
  },
  handler: (args) => {
    // (parameter) args: { a: unknown; b: unknown; }
    // Error: Object is of type 'unknown'. ts(2571)
    console.log(args.a + args.b);
  },
});

【问题讨论】:

  • 请分享您的handler 功能。我的意思是使用这些类型的代码
  • @captain-yossarian 添加

标签: typescript typescript-generics


【解决方案1】:

要从CommandArguments 推断CommandHandler,我们可能不想做相反的事情,即ArgumentName&lt;T[k]&gt; in

type CommandArguments<T> = { [k in keyof T]: ArgumentName<T[k]> };

上述方法的另一个问题是,从上面的声明中,CommandArguments 只限制了T 的keys,这就是为什么T 的属性value > 类型被推断为只是unknown。

一个简单的解决方案是约束arguments 的类型T 扩展ArgumentTypeName 的对象,然后从该类型T 推断handler 的args。这里我们需要使用T extends CommandArguments而不是CommandArguments,以便捕获实际的使用类型。

declare interface User{};
type ArgumentType<T> =
  T extends 'string' ? string :
  T extends 'number' ? number :
  T extends 'user'   ? User   :
  never;

type ArgumentTypeName = 'string' | 'number' | 'user';
type CommandArguments = { [k: string]: ArgumentTypeName };
type CommandHandler<T> = (args: { [k in keyof T]: ArgumentType<T[k]>}) => void;

type Command<T extends CommandArguments> = {
  name: string;
  arguments: T;
  handler: CommandHandler<T>;
}

const commands: Command<any>[] = [];
export function createCommand<T extends CommandArguments>(cmd: Command<T>) {
  commands.push(cmd);
}

createCommand({
  name: 'add',
  arguments: {
    a: 'number',
    b: 'string',
    c: 'user',
  },
  handler: (args) => {
    // (parameter) args: { a: number; b: string; c: User }
    console.log(args.a.toPrecision(2) + args.b.padStart(30));
  },
});

Playground Link

【讨论】:

  • 啊,将CommandArguments 提升到顶部的想法不知何故错过了我的想法。谢谢,这非常有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-07
  • 1970-01-01
  • 1970-01-01
  • 2019-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多