推断的泛型在泛型类型嵌套在对象中时是不同的。

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

Infered generic is different when a generic type is nested in an object

问题

在第一个示例中,您会看到null | undefined,而在第二个示例中没有。唯一的区别是第一个示例中的泛型类型嵌套在一个对象中。

这是因为在第一个示例中,您的toSignal函数的options参数被定义为包含initialValue属性的对象,该属性的类型是U。这意味着您可以传递一个对象,其中initialValue属性可以是nullundefined,因为U的范围是T|null|undefined。因此,在第一个示例中,foo的类型是Animal | null | undefined,因为您可以选择传递一个initialValue属性,其值为nullundefined

而在第二个示例中,toSignal2函数的initialValue参数直接定义为U,而不是包含在对象中。这样,您不能传递nullundefined作为initialValue,因为U的范围是T,所以foo3的类型只能是Animal

这就是为什么第一个示例中会出现null | undefined,而第二个示例中不会的原因。

英文:

If have this code :

type Signal<T> = {};

declare type Animal = 'cat' | 'dog';
declare const animal: Animal;

declare function toSignal<T, U extends T|null|undefined>(
    source: T,
    options: {initialValue: U}): Signal<T|U>;

const foo = toSignal(animal, {initialValue: 'cat'}) 
//    ^? 'Animal | null | undefined

const foo2 = toSignal(animal, {initialValue: 'cat' as const}) // workaround
//    ^? Animal

//////////////

declare function toSignal2<T, U extends T|null|undefined>(
    source: T,
    initialValue: U): Signal<T|U>;

const foo3 = toSignal2(animal, 'cat')
//    ^? Animal

My question is why do I get also null | undefined in the first example and not an the 2nd one.

The only difference is that the 2nd generic type is nested in an object.

Playground

答案1

得分: 2

以下是您要翻译的内容:

在以下情况下:

function toSignal<T, U extends T | null | undefined>(
  source: T,
  options: { initialValue: U },
): Signal<T | U>;

U 可以是 Tnullundefined,并且它被用作对象的属性,这意味着传递的对象的该属性也可以是 nullundefined

尽管在您的使用中,您肯定将字符串作为该对象的值传递,但编译器无法确定对象是否会发生变化,属性是否会变成您传递的字符串,因此最终会得到 T | null | undefined

在第二种情况下:

declare function toSignal2<T, U extends T | null | undefined>(
  source: T,
  initialValue: U,
): Signal<T | U>;

您传递的是一个字符串,而不是一个对象,字符串不能被更改,因为它是一种原始类型,因此最终看到的是预期的值。

至少有两种可行的解决方案,其中一种您已经使用过:

  1. const assertion
toSignal(animal, { initialValue: 'cat' as const });

const assertion 会将对象变为只读,编译器不会有 initialValue 可能为 null | undefined 的可能性。

  1. const type parameter(在 TypeScript >= 5.0.x 中可用)
    与第一种方法基本相同的逻辑,但更加优雅:
declare function toSignal<T, const U extends T | null | undefined>(
  source: T,
  options: { initialValue: U },
): Signal<T | U>;

playground

英文:

In the following case:

function toSignal&lt;T, U extends T | null | undefined&gt;(
  source: T,
  options: { initialValue: U },
): Signal&lt;T | U&gt;;

U can be either T, null, or undefined and it is used as a property in an object, which means that the passed object can have null or undefined in that property as well.

Even though in your usage you definitely pass a string as the value in that object, the compiler can't be sure that the object won't mutate and the property won't be the string that you passed, thus you get the T | null | undefined at the end.

In the second case:

declare function toSignal2&lt;T, U extends T | null | undefined&gt;(
  source: T,
  initialValue: U,
): Signal&lt;T | U&gt;;

You pass a string, not an object and a string can't be mutated, since it is a primitive type, thus you see the expected value at the end.

There are at least two available solutions one of which you already used:

  1. const assertion:
toSignal(animal, { initialValue: &#39;cat&#39; as const });

Const assertion will turn the object to read-only, and there will be no possibility of initialValue being null | undefined for the compiler.

  1. const type parameter (available with ts >= 5.0.x)
    Pretty much the same logic as in the first approach, but more elegant:
declare function toSignal&lt;T, const U extends T | null | undefined&gt;(
  source: T,
  options: { initialValue: U },
): Signal&lt;T | U&gt;;

playground

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

发表评论

匿名网友

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

确定