typescript 什么类型可用于将不被调用的泛型方法的参数?

0aydgbwb  于 2023-03-04  发布在  TypeScript
关注(0)|答案(3)|浏览(130)

我尝试定义一个方法,它的参数具有泛型类型,使用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

7ajki6be

7ajki6be1#

使用底部类型而不是顶部类型。
unknown是顶层类型:它包含了所有可能的值。一个带有unknown类型参数的函数应该接受任何值作为参数。一个只接受 some 类型值的函数不可能符合(_: unknown) => void;然而,对于任何T,函数(_: unknown) => void将符合(_: T) => void,因此我们有Tunknown的子类型,但另一方面,(_: unknown) => void(_: T) => void的子类型,这种情况被称为 * 逆变 *,并且我们说将T取为取T的函数类型的类型构造函数在T中是 * 逆变 * 的。
在您的示例中,由于Operation<T>的定义指定了一个属性,该属性的函数类型采用T,因此Operation<T>T中也是逆变的,这意味着对于任何其他TOperation<unknown>都是Operation<T>的 * 子类型 而不是 * 您想要的超类型。(当您更改Operation<T>以包含类型仅为T的属性时,它在T中变成了 * 协变 *,这意味着子类型关系是 * 不 * 反转的。)
相反,您可以使用Operation<never>

export interface Operation<Context> {
    process: (context: Context) => void;
    n: number;
}

type MyContext = {
    info: string;
}

const op : Operation<MyContext> = {
    process: (context: MyContext) =>
        { console.log("process", context.info); },
    n: 42
}

function fNever(op: Operation<never>): void {
    console.log("fNever", op.n);
}

console.log(fNever(op));

底层类型never不包含任何值,是所有类型的子类型。因为Operation<T>T中是逆变的,这意味着Operation<never>是所有Operation<T>的超类型。在更简单的语言中,通过使用Operation<never>,您不需要process可以用任何特定的东西调用。实际上你保证永远不会调用process因为never不包含值,所以接受never参数的函数是不可调用的。

k4emjkb1

k4emjkb12#

不能将Operation<MyContext>传递给参数类型为Operation<unknown>的函数,因为该函数可能会尝试非法修改该参数。如下所示:

function fUnknown(op: Operation<unknown>): void {
    const foo: unknown = "Mystery";
    op.context = foo;
    console.log("fUnknown", op.n);
}

您可能应该这样做:

function fUnknown2<T extends unknown>(op: Operation<T>): void {
    const foo: unknown = "Mystery";
    /* Would cause "Type 'unknown' is not assignable to type 'T'.
  'unknown' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'unknown'."
    op.context = foo; */
    console.log("fUnknown", op.n);
}
js81xvg6

js81xvg63#

它不能编译的原因是函数参数是逆变的,并且Operation在函数参数位置使用Context类型参数。
当您将op传递给fUnknown时,op声称它只能与MyContext一起工作,但是fUnknown要求传递给它的Operation可以与任何东西一起工作,而不仅仅是MyContext
您可以通过多种方式解决此问题:

  • 需要Operation<any>:如果在函数体或签名的其余部分中根本不使用泛型信息,这是完全有效的选择
  • 需要Operation<MyContext>:它不是很通用
  • 要求Operation<T>,并让T被推断:当您传递op时,T将变为MyContext,并且您可以在函数主体/签名中使用T是泛型的信息

相关问题