繁体   English   中英

使用具有强类型字段的对象设置通用类型可选映射

[英]Setting a generic typed optional map with an object with a strongly typed field

我正在尝试编写代码来注册强类型数据加载器,但是我在使用打字稿无法正确设置地图时遇到了麻烦。 在下面的示例中, M是服务映射, k是具有字段类型的服务列表,该字段类型为E的确定值。 但是,当我尝试将实例分配给地图时,它给了我一个错误,说我正在尝试分配给未定义。 不知道从这里去哪里。

enum E {
  A = 'a',
  B = 'b',
}

interface I<T extends E> {
  type: T;
}

type J<T> = T extends E ? I<T> : never;
export type K = J<E>;

const M: { [T in E]?: I<T> } = {};
const k: K[] = [];

k.forEach(
  <T extends E>(i: I<T>) => {
    M[i.type] = i;
    // ERROR
    // Type 'I<T>' is not assignable to type '{ a?: I<E.A> | undefined; b?: I<E.B> | undefined; }[T]'.
    //  Type 'I<T>' is not assignable to type 'undefined'.
  });

这是当前 TypeScript 编译器的限制,或者可能是多个此类限制的组合。 我在这里看到的相关问题是:

  • microsoft/TypeScript# extends_oneof (支持extends_oneof通用约束))和microsoft/TypeScript#25879 (支持非通用映射类型的通用索引,正是您上面的用例):

    目前还没有办法说,一个类型参数约束为工会(像你T extends E以上)只能走联合的同时一个单独的元素。 现在, T extends E意味着可以使用以下四种类型中的任何一种来指定TneverEAEBEA | EB EA | EB (这只是E )。 如果有可能,告诉编译器T可能是EA可能是EB ,但它不能成为他们的工会,那么也许编译器可以知道你的分配工作,对于那些每一个接受两个他们。

    但事实并非如此。 T可以用EA | EB指定 EA | EB ,所以M[i.type] = i必须为这种情况工作。 因此,您的通用回调函数也可能更改为非通用版本

    k.forEach((i: I<E>) => { M[i.type] = i; }); // error! // --------------------> ~~~~~~~~~ // Type 'I<E>' is not assignable to type 'undefined'

    尽其所能。 由于其他原因,此版本也会出错:

  • microsoft/TypeScript#30581 (支持关联类型), microsoft/TypeScript#25051 (支持分布式控制流分析):

    让我们假设i的类型为I<EA> | I<EB> I<EA> | I<EB> 不幸的是,编译器无法执行必要的高阶推理,以确保M[i.type] = i是可接受的。 在赋值的左侧, M[i.type]被视为类型M[EA] | M[EB] M[EA] | M[EB] 但是因为您试图为该类型分配一个值,所以编译器要求分配的值是这些类型的交集而不是union 这是通过microsoft/TypeScript#30769 在 TypeScript 3.5引入的,作为提高安全性的一种方式。 因此,左侧的M[i.type]被视为类型M[EA] & M[EB] ,即(I<EA> | undefined) & (I<EB> | undefined) 由于I<EA> & I<EB> never ,这简化为undefined 右边的i的类型是I<EA> | I<EB> I<EA> | I<EB> ,它不能分配给undefined ,所以有一个错误。

    例如,如果我们有两个值ij ,它们都是I<EA> | I<EB>类型,那么这个错误就会有意义I<EA> | I<EB> I<EA> | I<EB>并尝试分配M[i.type] = j ;。 编译器会合理地抱怨,因为j不可能同时I<EA>I<EB> ,那么将它分配给M[i.type]i.type !== j.type ,因为可能i.type !== j.type 因为编译器只注重类型的明显安全M[i.type] = i ,它目前还不能告诉大家,和明显的不安全之间的差异M[i.type] = j

    我们告诉编译器是的类型M[i.type]i都彼此相关的:尽管他们都是联盟类型,这是不可能的M[i.type]为类型I<EA>iI<EB>类型。 但这目前是不可能的。


那么你能做些什么来解决呢? 您可以做的最简单的事情(可能也是正确的做法)是使用类型断言 知道你在做什么是安全的,所以告诉编译器不要担心。 最简单的断言是在那里扔any手榴弹:

k.forEach(
  (i) => {
    M[i.type] = i as any;
  });

您可以使用一些不安全的断言:

k.forEach(
  <T extends E>(i: I<T>) => {
    M[i.type] = i as any as (typeof M)[T];
  });

您保留通用回调并告诉编译器分配是安全的,或者:

k.forEach(
  <T extends E>(i: I<T>) => {
    (M as { [K in T]?: I<K> })[i.type] = i;
  });

在其中保留通用回调并告诉编译器M可以被视为只有T类型的键。 这个可能是我最喜欢的,因为它最接近表达你想要做的事情。

另一个可能的解决方法是依次将i显式缩小为I<EA>I<EB> ,此时编译器的控制流分析可以接管:

k.forEach(i => i.type === E.A ?
  M[i.type] = i :
  M[i.type] = i
)

显然,这是多余的,而不是您想要做的。 但它确实表明编译器原则上可以理解M[i.type] = i是安全的,如果可以告诉编译器假装执行了这样的案例枚举(如在 microsoft/TypeScript#25051 中) .

Playground 链接到代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM