英文:
Infered generic is different when a generic type is nested in an object
问题
在第一个示例中,您会看到null | undefined
,而在第二个示例中没有。唯一的区别是第一个示例中的泛型类型嵌套在一个对象中。
这是因为在第一个示例中,您的toSignal
函数的options
参数被定义为包含initialValue
属性的对象,该属性的类型是U
。这意味着您可以传递一个对象,其中initialValue
属性可以是null
或undefined
,因为U
的范围是T|null|undefined
。因此,在第一个示例中,foo
的类型是Animal | null | undefined
,因为您可以选择传递一个initialValue
属性,其值为null
或undefined
。
而在第二个示例中,toSignal2
函数的initialValue
参数直接定义为U
,而不是包含在对象中。这样,您不能传递null
或undefined
作为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.
答案1
得分: 2
以下是您要翻译的内容:
在以下情况下:
function toSignal<T, U extends T | null | undefined>(
source: T,
options: { initialValue: U },
): Signal<T | U>;
U
可以是 T
、null
或 undefined
,并且它被用作对象的属性,这意味着传递的对象的该属性也可以是 null
或 undefined
。
尽管在您的使用中,您肯定将字符串作为该对象的值传递,但编译器无法确定对象是否会发生变化,属性是否会变成您传递的字符串,因此最终会得到 T | null | undefined
。
在第二种情况下:
declare function toSignal2<T, U extends T | null | undefined>(
source: T,
initialValue: U,
): Signal<T | U>;
您传递的是一个字符串,而不是一个对象,字符串不能被更改,因为它是一种原始类型,因此最终看到的是预期的值。
至少有两种可行的解决方案,其中一种您已经使用过:
toSignal(animal, { initialValue: 'cat' as const });
const assertion 会将对象变为只读,编译器不会有 initialValue
可能为 null | undefined
的可能性。
- 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>;
英文:
In the following case:
function toSignal<T, U extends T | null | undefined>(
source: T,
options: { initialValue: U },
): Signal<T | U>;
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<T, U extends T | null | undefined>(
source: T,
initialValue: U,
): Signal<T | U>;
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:
toSignal(animal, { initialValue: 'cat' 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.
- 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<T, const U extends T | null | undefined>(
source: T,
options: { initialValue: U },
): Signal<T | U>;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论