动态根据数据指定参数类型。

huangapple go评论65阅读模式
英文:

Dynamically specify argument types based on data

问题

我有一个定义了一些变换函数的对象。这些函数具有相同数量的参数,但参数类型不同。我正在尝试找到一种方法,让 TypeScript 根据函数名称推断参数类型。

假设我们有一个包含一些函数的对象:

const funcList = {
  f1: (val: string, opts: number) => { console.log(val, opts); },
  f2: (val: number, opts: boolean) => { console.log(val, opts); },
} as const;

还有一个数据集需要我们迭代:

const data = [{
  func: 'f1',
  val: 'strval',
  opt: 111,
}, {
  func: 'f2',
  val: 222,
  opt: true,
}] as const;

现在,我想要遍历该数据数组并调用相应的函数:

for (const e of data) {
  funcList[e.func](e.val, e.opt);
}

TypeScript 不允许此代码,并显示以下错误:

TS2345: 类型 'string | number' 的参数不能赋值给类型 'never' 的参数。 类型 'string' 不能赋值给类型 'never'。

似乎 TypeScript 不根据 func 字段来区分类型。但是,如果我将代码更改如下,一切都能正常工作:

for (const e of data) {
  if (e.func === 'f1') {
    funcList[e.func](e.val, e.opt);
  } else {
    funcList[e.func](e.val, e.opt);
  }
}

因此,如果我明确检查元素的 func 字段,TypeScript 就可以正确推断其余部分的类型。

我可以做什么来避免使用大量的 if/switch 语句?

英文:

I have an object which defines some transform functions. Those functions have the same number of parameters but different parameter types. I'm trying to figure out a way to make TS to infer parameter types based on a function name.

Let's say we have an object with some functions:

const funcList = {
  f1: (val: string, opts: number) => { console.log(val, opts); },
  f2: (val: number, opts: boolean) => { console.log(val, opts); },
} as const;

And there is data set that we need to iterate through

const data = [{
  func: 'f1',
  val: 'strval',
  opt: 111,
}, {
  func: 'f2',
  val: 222,
  opt: true,
}] as const;

And now I want to iterate over that data array and call a corresponding function

for (const e of data) {
  funcList[e.func](e.val, e.opt);
}

TS doesn't allow this code with the following error

> TS2345: Argument of type 'string | number' is not assignable to parameter of type 'never'.   Type 'string' is not assignable to type 'never'.

It seems that TS doesn't discriminate type based on func field.
However if I change the code as the following, everything works

for (const e of data) {
  if (e.func === 'f1') {
    funcList[e.func](e.val, e.opt);
  } else {
    funcList[e.func](e.val, e.opt);
  }
}

So if I explicitly check element's func field, TS can then infer the rest with proper types.

What could I do to avoid using tons of ifs/switch?

答案1

得分: 1

编译器无法处理 ms/TS#30581 中描述的“相关联的联合类型”,但是在 ms/TS#47109 中有一个建议的重构,考虑转向泛型。

首先,我们需要一个将所有与函数类型相关的内容都保存在其中的映射类型:

type RecordMap = {
  f1: {
    args: { val: string; opt: number };
    return: number;
  };
  f2: {
    args: { val: number };
    return: void;
  };
};

现在,让我们为 funcList 创建一个类型,我们将使用 映射类型。我们将遍历 RecordMap 的键,并从其每个元素中获取相关类型:

type FuncList = {
  [K in keyof RecordMap]: (
    args: RecordMap[K]['args'],
  ) => RecordMap[K]['return'];
};

结果:

type FuncList = {
    f1: (args: {
        val: string;
        opt: number;
    }) => number;
    f2: (args: {
        val: number;
    }) => void;
}

让我们在 funcList 上使用 FuncList

const funcList: FuncList = {
  f1: (args) => {
    return args.opt;
  },
  f2: (args) => {
    console.log(args);
  },
};

我们不需要在 funcList 中显式包含类型,因为它们是从 FuncList 推断出来的。

接下来,我们需要为函数参数创建一个映射类型,它将返回传递的函数名称或如果未传递名称则返回可能的参数联合:

type FuncListArgs<T extends keyof RecordMap = keyof RecordMap> = {
  [K in T]: {
    func: K;
  } & RecordMap[K]['args'];
}[T];

结果:

// {
//     func: "f1";
// } & {
//     val: string;
//     opt: number;
// }
type F1Args = FuncListArgs<'f1'>

// ({
//     func: "f1";
// } & {
//     val: string;
//     opt: number;
// }) | ({
//     func: "f2";
// } & {
//     val: number;
// })
type AllArgs = FuncListArgs

由于我们需要一个泛型参数来使整个过程工作,我们将创建一个接受某个函数的参数的函数:

const handleFunc = <T extends keyof RecordMap>(args: FuncListArgs<T>) => {
  const func = funcList[args.func];
  return func(args); // 无错误
};

用法:

for (const a of data) {
  handleFunc(a);
}

playground

英文:

The compiler is unable to handle the "correlated union types" described in ms/TS#30581, however, there is a suggested refactor described in ms/TS#47109, which considers moving to generics.

First, we will need some map type that will hold the everything related to types of functions:

type RecordMap = {
  f1: {
    args: { val: string; opt: number };
    return: number;
  };
  f2: {
    args: { val: number };
    return: void;
  };
};

Now, let's create a type for funcList, which we will do using mapped types. We will map through RecordMap keys and take relevant types from each of its element:

type FuncList = {
  [K in keyof RecordMap]: (
    args: RecordMap[K][&#39;args&#39;],
  ) =&gt; RecordMap[K][&#39;return&#39;];
};

Result:

type FuncList = {
    f1: (args: {
        val: string;
        opt: number;
    }) =&gt; number;
    f2: (args: {
        val: number;
    }) =&gt; void;
}

Let's use FuncList on the funcList:

const funcList: FuncList = {
  f1: (args) =&gt; {
    return args.opt;
  },
  f2: (args) =&gt; {
    console.log(args);
  },
};

We don't need to include types in the funcList explicitly, since they are inferred from FuncList.

Next, we need to create a mapped type for the function arguments, which will return a union of possible arguments, for passed name of the function or for all of them if name is not passed:

type FuncListArgs&lt;T extends keyof RecordMap = keyof RecordMap&gt; = {
  [K in T]: {
    func: K;
  } &amp; RecordMap[K][&#39;args&#39;];
}[T];

Result:

// {
//     func: &quot;f1&quot;;
// } &amp; {
//     val: string;
//     opt: number;
// }
type F1Args = FuncListArgs&lt;&#39;f1&#39;&gt;

// ({
//     func: &quot;f1&quot;;
// } &amp; {
//     val: string;
//     opt: number;
// }) | ({
//     func: &quot;f2&quot;;
// } &amp; {
//     val: number;
// })
type AllArgs = FuncListArgs

Since we need a generic argument to make this whole thing to work, we will create a function that will accept an args for some function:

const handleFunc = &lt;T extends keyof RecordMap&gt;(args: FuncListArgs&lt;T&gt;) =&gt; {
  const func = funcList[args.func];
  return func(args); // no error
};

Usage:

for (const a of data) {
  handleFunc(a);
}

playground

huangapple
  • 本文由 发表于 2023年6月13日 00:39:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76458677.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定