我尝试定义一个方法,它的参数具有泛型类型,使用unknown作为泛型类型,因为我不需要它:function f(op: Operation<unknown>): void {...}
。它不是在所有情况下都有效,如果Operation在方法签名中使用其泛型类型,它就不起作用。
如果直接使用泛型Context成员而不是在参数中具有泛型Context的方法,则编译时不会出错。
有人能解释一下,如果泛型在方法的签名中,为什么我不能使用unknown
?
我试图找出为什么这个示例不能编译:
export interface Operation<Context> {
process: (context: Context) => void;
//context: Context;
n:number;
}
type MyContext = {
info: string;
}
const op : Operation<MyContext> = {
process: (context: MyContext) => { console.log("process",context.info); },
//context: { info:"context.info" },
n:42
}
function fGeneric<Context>(op: Operation<Context>): void {
console.log("fGeneric", op.n);
}
console.log(fGeneric(op));
function fUnknown(op: Operation<unknown>): void {
console.log("fUnknown", op.n);
}
console.log(fUnknown(op));
// Argument of type 'Operation<MyContext>' is not assignable to parameter of type 'Operation<unknown>'.
// Type 'unknown' is not assignable to type 'MyContext'.
注解掉process
和取消注解context
将编译无误。
(显然,这是一个简化的例子,可以将问题简化到最小程度。)
Playground:https://www.typescriptlang.org/play?ts=4.9.5#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgeTDUxgQiQB4Bhc1EGAPjgG8AoODuMKCXAZz4AuOAApstUDGE0UkgJRwAvEwBuEBABMA3O04B6PeNn1pE+js5wkgpAFcAtgCM0OgL6tWMAJ5E4AWS8ZOnhFFl1kdAhhPhgoZABzNw8jGLgIMDhhQmJScgoAoMkmULYObl5gAWExMyl-QNqFZRY4FIgAG2AAOnaIeJEAInL+PgGAGiNgroiIOS04VzHdA0nJYWZEJEjBAdX6aa2IAYWljmsAFgAmVndWdFskbFykOHQAcWAkNARsalqGETpLJEKAkMiUQr0BhyYRqTRhSytch8DrdXr9AbvT7fbDjNJgLpIOY3ZLI1E9PoiLFfOLYQFgOTEu4PJ7g14AVSQAGskBAAO5IenAnLgigPHn8pDQ2HqDQIyxtToUjHoTkSgV49KE4m3RVoymq7m8gX0xlaIA
3条答案
按热度按时间7ajki6be1#
使用底部类型而不是顶部类型。
unknown
是顶层类型:它包含了所有可能的值。一个带有unknown
类型参数的函数应该接受任何值作为参数。一个只接受 some 类型值的函数不可能符合(_: unknown) => void
;然而,对于任何T
,函数(_: unknown) => void
将符合(_: T) => void
,因此我们有T
是unknown
的子类型,但另一方面,(_: unknown) => void
是(_: T) => void
的子类型,这种情况被称为 * 逆变 *,并且我们说将T
取为取T
的函数类型的类型构造函数在T
中是 * 逆变 * 的。在您的示例中,由于
Operation<T>
的定义指定了一个属性,该属性的函数类型采用T
,因此Operation<T>
在T
中也是逆变的,这意味着对于任何其他T
,Operation<unknown>
都是Operation<T>
的 * 子类型 。 而不是 * 您想要的超类型。(当您更改Operation<T>
以包含类型仅为T
的属性时,它在T
中变成了 * 协变 *,这意味着子类型关系是 * 不 * 反转的。)相反,您可以使用
Operation<never>
:底层类型
never
不包含任何值,是所有类型的子类型。因为Operation<T>
在T
中是逆变的,这意味着Operation<never>
是所有Operation<T>
的超类型。在更简单的语言中,通过使用Operation<never>
,您不需要process
可以用任何特定的东西调用。实际上你保证永远不会调用process
因为never
不包含值,所以接受never
参数的函数是不可调用的。k4emjkb12#
不能将
Operation<MyContext>
传递给参数类型为Operation<unknown>
的函数,因为该函数可能会尝试非法修改该参数。如下所示:您可能应该这样做:
js81xvg63#
它不能编译的原因是函数参数是逆变的,并且
Operation
在函数参数位置使用Context
类型参数。当您将
op
传递给fUnknown
时,op
声称它只能与MyContext
一起工作,但是fUnknown
要求传递给它的Operation
可以与任何东西一起工作,而不仅仅是MyContext
。您可以通过多种方式解决此问题:
Operation<any>
:如果在函数体或签名的其余部分中根本不使用泛型信息,这是完全有效的选择Operation<MyContext>
:它不是很通用Operation<T>
,并让T
被推断:当您传递op
时,T
将变为MyContext
,并且您可以在函数主体/签名中使用T
是泛型的信息