Typescript通用条件不精确类型

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

Typescript generic conditional does not precise the type

问题

这是一段代码(playground,Typescript 5.0.4),它接受一个泛型函数,并根据类型执行某些操作:

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. function MyFunction<T extends keyof TypeToValue>(
  6. value: TypeToValue[T],
  7. type: T,
  8. ) {
  9. if (type === 'one') return value.toFixed(2); // 报错 "Property 'toFixed' does not exist on type 'string | number'."
  10. if (type === 'two') return value.toString()
  11. throw Error('invalid type')
  12. }

然而,我期望它不会报错,因为Typescript应该意识到在type === 'one'之后,泛型T会被推断为'one',因此value的类型应该是number

是否有一种方法可以自动推断类型?

谢谢!

英文:

Here is a piece of code (playground, Typescript 5.0.4) that takes a generic function, and do some operation depending on the type:

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. function MyFunction&lt;T extends keyof TypeToValue&gt;(
  6. value: TypeToValue[T],
  7. type: T,
  8. ) {
  9. if (type === &#39;one&#39;) return value.toFixed(2); // fail &quot;Property &#39;toFixed&#39; does not exist on type &#39;string | number&#39;.&quot;
  10. if (type === &#39;two&#39;) return value.toString()
  11. throw Error(&#39;invalid type&#39;)
  12. }

However, I expect it not to fail because Typescript should be aware that after type === &#39;one&#39;, then the generic T is &#39;one&#39; hence value is typed as number.

Is there a workaround to have the type automatically inferred?

Thanks !

答案1

得分: 1

我认为推断不够智能,无法理解正确的类型。不过,这只是我的个人意见。

无论如何,这里有一个稍微不同的版本,在这个版本中,它只是在运行时评估类型。这也确保了正确的推断。

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. function MyFunction<T extends keyof TypeToValue>(
  6. value: TypeToValue[T],
  7. // type: T,
  8. ) {
  9. if (typeof value === 'number') return value.toFixed(2);
  10. if (typeof value === 'string') return value.toString()
  11. throw Error('invalid type')
  12. }
  13. let s: string;
  14. s = MyFunction<"one">(123.456);
  15. console.log(s); //打印 "123.46"
  16. s = MyFunction<"two">("hello");
  17. console.log(s); //打印 "hello"

也不需要第二个参数。

英文:

I think the inference is not smart enough to understand the proper type. However, that's just my humble opinion.

Anyway, here is a slightly different version, where it simply evaluates the type at runtime. That also ensures the proper inference.

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. function MyFunction&lt;T extends keyof TypeToValue&gt;(
  6. value: TypeToValue[T],
  7. // type: T,
  8. ) {
  9. if (typeof value === &#39;number&#39;) return value.toFixed(2);
  10. if (typeof value === &#39;string&#39;) return value.toString()
  11. throw Error(&#39;invalid type&#39;)
  12. }
  13. let s: string;
  14. s = MyFunction&lt;&quot;one&quot;&gt;(123.456);
  15. console.log(s); //prints &quot;123.46&quot;
  16. s = MyFunction&lt;&quot;two&quot;&gt;(&quot;hello&quot;);
  17. console.log(s); //prints &quot;hello&quot;

Also no need of the second argument.

答案2

得分: 0

你需要为你的任务使用更通用的东西。

  1. interface Interface1 {
  2. a: string;
  3. b: number;
  4. }
  5. interface Interface2 {
  6. c: boolean;
  7. d: any;
  8. }
  9. function isInterface<T>(value: any): value is T {
  10. if (typeof value !== 'object' || value === null) {
  11. return false;
  12. }
  13. for (const key in value) {
  14. if (value[key] === undefined) {
  15. return false;
  16. }
  17. }
  18. return true;
  19. }
  20. type TypeToValue = number | string | Interface1 | Interface2;
  21. function MyFunction(t: TypeToValue) {
  22. if (typeof t === "number") {
  23. return t.toFixed(2);
  24. }
  25. if (typeof t === "string") {
  26. return t.toString();
  27. }
  28. if (isInterface<Interface1>(t)) {
  29. console.log('processing obj of type Interface1');
  30. return t; // or do something with object of type Interface1
  31. }
  32. if (isInterface<Interface2>(t)) {
  33. console.log('processing obj of type Interface2');
  34. return t; // or do something with object of type Interface2
  35. }
  36. throw new Error("Invalid type");
  37. }
  38. // test
  39. const obj1 = { a: 'hello', b: 100 };
  40. const obj2 = { c: true, d: 'world' };
  41. console.log(MyFunction(100));
  42. console.log(MyFunction("one hundred"));
  43. console.log(MyFunction(obj1));
  44. console.log(MyFunction(obj2));
英文:

You need something more generic for your task. Playground

  1. interface Interface1 {
  2. a: string;
  3. b: number;
  4. }
  5. interface Interface2 {
  6. c: boolean;
  7. d: any;
  8. }
  9. function isInterface&lt;T&gt;(value: any): value is T {
  10. if (typeof value !== &#39;object&#39; || value === null) {
  11. return false;
  12. }
  13. for (const key in value) {
  14. if (value[key] === undefined) {
  15. return false;
  16. }
  17. }
  18. return true;
  19. }
  20. type TypeToValue = number | string | Interface1 | Interface2;
  21. function MyFunction(t: TypeToValue) {
  22. if (typeof t === &quot;number&quot;) {
  23. return t.toFixed(2);
  24. }
  25. if (typeof t === &quot;string&quot;) {
  26. return t.toString();
  27. }
  28. if (isInterface&lt;Interface1&gt;(t)) {
  29. console.log(&#39;processing obj of type Interface1&#39;);
  30. return t; // or do something with object of type Interface1
  31. }
  32. if (isInterface&lt;Interface2&gt;(t)) {
  33. console.log(&#39;processing obj of type Interface2&#39;);
  34. return t; // or do something with object of type Interface2
  35. }
  36. throw new Error(&quot;Invalid type&quot;);
  37. }
  38. // test
  39. const obj1 = { a: &#39;hello&#39;, b: 100 };
  40. const obj2 = { c: true, d: &#39;world&#39; };
  41. console.log(MyFunction(100));
  42. console.log(MyFunction(&quot;one hundred&quot;));
  43. console.log(MyFunction(obj1));
  44. console.log(MyFunction(obj2));

答案3

得分: 0

感谢 @jcalz 提供了一个有效的解决方法(示例):

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. type MyFunctionArgs = { [K in keyof TypeToValue]: [value: TypeToValue[K], type: K] }[keyof TypeToValue]
  6. function MyFunction(...[value, type]: MyFunctionArgs) {
  7. if (type === 'one') return value.toFixed(2); // okay
  8. if (type === 'two') return value.toString()
  9. throw Error('invalid type')
  10. }
英文:

Thank you @jcalz for providing a valid workaround (playground):

  1. type TypeToValue = {
  2. one: number;
  3. two: string;
  4. };
  5. type MyFunctionArgs = { [K in keyof TypeToValue]: [value: TypeToValue[K], type: K] }[keyof TypeToValue]
  6. function MyFunction(...[value, type]: MyFunctionArgs) {
  7. if (type === &#39;one&#39;) return value.toFixed(2); // okay
  8. if (type === &#39;two&#39;) return value.toString()
  9. throw Error(&#39;invalid type&#39;)
  10. }

huangapple
  • 本文由 发表于 2023年5月26日 12:32:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76337693.html
匿名

发表评论

匿名网友

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

确定