typescript - 回调参数的条件类型

标签 typescript types

这些是我的类型:

export type Mapping =
    | {
          type: 'ok';
          data: Record<string, string>;
      }
    | {
          type: 'error';
          data: Error;
      };

export type Payload<T> = Extract<Mapping, { type: T }>['data'];

export interface Callback<T> {
    (data: Payload<T>): void;
}

export type Store = {
    [K in Mapping['type']]: Set<Callback<K>>;
};

这是我的代码:

const storage: Store = {
    ok: new Set(),
    error: new Set(),
};

function store<T extends Mapping['type']>(
    type: T,
    callback: Callback<T>
): void {
    storage[type].add(callback);
}

我得到的错误:

Argument of type 'Callback<T>' is not assignable to parameter of type 'Callback<"ok"> & Callback<"error">'.
  Type 'Callback<T>' is not assignable to type 'Callback<"ok">'.
    Types of parameters 'data' and 'data' are incompatible.
      Type 'Record<string, string>' is not assignable to type 'Payload<T>'.
        Type 'Record<string, string>' is not assignable to type 'Extract<{ type: "error"; data: Error; }, { type: T; }>["data"]'.
          Type 'Record<string, string>' is missing the following properties from type 'Error': name, messagets(2345)

我想实现,如果某个回调存储在 storage.ok 中或storage.error可以处理正确的参数类型。所以我尝试使用条件类型,轻松地为回调参数类型添加更多选项。我认为错误在 'Callback<"ok"> & Callback<"error">'因为它不应该是& 。但错误在哪里呢? storage有正确的类型:

type Store = {
    error: Set<Callback<"error">>;
    ok: Set<Callback<"ok">>;
}

编辑:问题来自.add功能:

(method) Set<T>.add(value: Callback<"ok"> & Callback<"error">): Set<Callback<"ok">> | Set<Callback<"error">>

哪里来的&来自?

最佳答案

这是编译器无法执行必要的高阶类型分析来验证您在store()的实现中所做的事情。是安全的。


让我们定义PickStore<T> ,像这样:

type PickStore<T extends Mapping['type']> = { [K in T]: Set<Callback<K>> };

这本质上与 Pick<Store, T> 相同因此是 Store 的父类(super class)型,但编译器无法进行高阶类型分析来验证这一点。对于 T 的任何特定值,编译器可以看到PickStore<T>Store 的父类(super class)型:

const sOk: PickStore<"ok"> = storage; // okay
const sError: PickStore<"error"> = storage; // okay
const sBoth: PickStore<"ok" | "error"> = storage; // okay
const sNeither: PickStore<never> = storage; // okay

但是当您尝试对通用执行相同操作时 T ,编译器犹豫不决:

function test<T extends Mapping['type']>() {
  const sT: PickStore<T> = storage; // error!
  // Type 'Store' is not assignable to type 'PickStore<T>'
}

这就是你的问题。如果你能扩大storagePickStore<T> store里面函数,编译器将能够看到 storage[type]类型为Set<Callback<T>> ,因此您可以 add() callback参数没有问题。

相反,编译器只理解 storage[type]可分配给联盟 Set<Callback<"ok">> | Set<Callback<"error"> 。这是事实,但对于您的目的来说不够具体。 storage[type] 之间的相关callback丢失了。 (有关编译器无法跟踪多个值之间类型相关性的问题,请参阅 microsoft/TypeScript#30581。)因此 add()方法被视为两个函数类型的联合。并且,当您 call a union of function types ,编译器需要其参数的交集,以保证类型安全。 (如果您知道您有一个函数是 X -taker 或 Y -taker,但您不确定是哪一个,那么为了安全起见,您最好给它一个 X & Y。)


我建议,在编译器无法验证某些高阶类型分析的情况下,您可以使用 type assertion只是告诉编译器你正在做的事情是安全的。这将保证安全的负担从编译器转移到了您身上,因此您应该小心。无论如何,它可能看起来像这样:

function store<T extends Mapping['type']>(
  type: T,
  callback: Callback<T>
): void {
  (storage as any as PickStore<T>)[type].add(callback);
}

在这里,我们刚刚向编译器断言 storage可以被视为 PickStore<T> (我们需要通过 unknownany 进行中间断言,因为编译器确实无法处理这种关系)。过了这个障碍,生产线的其余部分就顺利通过了。


Playground link to code

关于typescript - 回调参数的条件类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65368468/

相关文章:

javascript - 显示来自 Web 服务的数据(Js、Angular)

javascript - 如何修复 TypeScript 0.9 中的 TS2081 && TS2087 错误

c - 阅读 C 指针类型声明

angularjs - 在 mat-table 的 mat-cell 中实现 if else 条件 - Angular 5

Angular5 HttpClient,在GET api调用后设置变量

haskell - 什么是单态性限制?

php - 类型提示及其局限性背后的原因

haskell - 强制幻影型

types - 这个概念是否有一个术语,它是否存在于静态类型语言中?

node.js - TypeScript 与现有 Node js Express 项目集成