Typescript通用条件不精确类型

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

Typescript generic conditional does not precise the type

问题

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

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

然而,我期望它不会报错,因为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:

type TypeToValue = {
  one: number;
  two: string;
};
function MyFunction&lt;T extends keyof TypeToValue&gt;(
  value: TypeToValue[T],
  type: T,
) {
  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;
  if (type === &#39;two&#39;) return value.toString()
  throw Error(&#39;invalid type&#39;)
}

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

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

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

type TypeToValue = {
  one: number;
  two: string;
};
function MyFunction<T extends keyof TypeToValue>(
  value: TypeToValue[T],
  //  type: T,
) {
  if (typeof value === 'number') return value.toFixed(2);
  if (typeof value === 'string') return value.toString()
  throw Error('invalid type')
}

let s: string;
s = MyFunction<"one">(123.456);
console.log(s);    //打印 "123.46" 

s = MyFunction<"two">("hello");
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.

type TypeToValue = {
  one: number;
  two: string;
};
function MyFunction&lt;T extends keyof TypeToValue&gt;(
  value: TypeToValue[T],
  //  type: T,
) {
  if (typeof value === &#39;number&#39;) return value.toFixed(2);
  if (typeof value === &#39;string&#39;) return value.toString()
  throw Error(&#39;invalid type&#39;)
}

let s: string;
s = MyFunction&lt;&quot;one&quot;&gt;(123.456);
console.log(s);    //prints &quot;123.46&quot; 

s = MyFunction&lt;&quot;two&quot;&gt;(&quot;hello&quot;);
console.log(s);    //prints &quot;hello&quot;

Also no need of the second argument.

答案2

得分: 0

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

interface Interface1 {
    a: string;
    b: number;
}

interface Interface2 {
    c: boolean;
    d: any;
}

function isInterface<T>(value: any): value is T {
    if (typeof value !== 'object' || value === null) {
        return false;
    }
    for (const key in value) {
        if (value[key] === undefined) {
            return false;
        }
    }
    return true;
}

type TypeToValue = number | string | Interface1 | Interface2;

function MyFunction(t: TypeToValue) {
    if (typeof t === "number") {
        return t.toFixed(2);
    }

    if (typeof t === "string") {
        return t.toString();
    }

    if (isInterface<Interface1>(t)) {
        console.log('processing obj of type Interface1');
        return t; // or do something with object of type Interface1
    }

    if (isInterface<Interface2>(t)) {
        console.log('processing obj of type Interface2');
        return t; // or do something with object of type Interface2
    }

    throw new Error("Invalid type");
}

// test
const obj1 = { a: 'hello', b: 100 };
const obj2 = { c: true, d: 'world' };
console.log(MyFunction(100));
console.log(MyFunction("one hundred"));
console.log(MyFunction(obj1));
console.log(MyFunction(obj2));
英文:

You need something more generic for your task. Playground

interface Interface1 {
a: string;
b: number;
}
interface Interface2 {
c: boolean;
d: any;
}
function isInterface&lt;T&gt;(value: any): value is T {
if (typeof value !== &#39;object&#39; || value === null) {
return false;
}
for (const key in value) {
if (value[key] === undefined) {
return false;
}
}
return true;
}
type TypeToValue = number | string | Interface1 | Interface2;    
function MyFunction(t: TypeToValue) {
if (typeof t === &quot;number&quot;) {
return t.toFixed(2);
}
if (typeof t === &quot;string&quot;) {
return t.toString();
}
if (isInterface&lt;Interface1&gt;(t)) {
console.log(&#39;processing obj of type Interface1&#39;);
return t; // or do something with object of type Interface1
}
if (isInterface&lt;Interface2&gt;(t)) {
console.log(&#39;processing obj of type Interface2&#39;);
return t; // or do something with object of type Interface2
}
throw new Error(&quot;Invalid type&quot;);
}
// test
const obj1 = { a: &#39;hello&#39;, b: 100 };
const obj2 = { c: true, d: &#39;world&#39; };
console.log(MyFunction(100));
console.log(MyFunction(&quot;one hundred&quot;));
console.log(MyFunction(obj1));
console.log(MyFunction(obj2)); 

答案3

得分: 0

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

type TypeToValue = {
  one: number;
  two: string;
};

type MyFunctionArgs = { [K in keyof TypeToValue]: [value: TypeToValue[K], type: K] }[keyof TypeToValue]

function MyFunction(...[value, type]: MyFunctionArgs) {
  if (type === 'one') return value.toFixed(2); // okay
  if (type === 'two') return value.toString()
  throw Error('invalid type')
}
英文:

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

type TypeToValue = {
  one: number;
  two: string;
};


type MyFunctionArgs = { [K in keyof TypeToValue]: [value: TypeToValue[K], type: K] }[keyof TypeToValue]

function MyFunction(...[value, type]: MyFunctionArgs) {
  if (type === &#39;one&#39;) return value.toFixed(2); // okay
  if (type === &#39;two&#39;) return value.toString()
  throw Error(&#39;invalid type&#39;)
}

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:

确定