【问题标题】:Typescript type composition via Generics通过泛型的打字稿类型组合
【发布时间】:2022-01-04 16:36:49
【问题描述】:

在工厂使用了几次之后,我很难保持动态生成的类型(通过泛型)。比文字更能说明问题的例子:Typescript Playground

type ModularData = {};
//A module just hold data
type Module<D extends ModularData> = {
  data: D;
};

function moduleFactory<D>(data: D): Module<D> {
  return { data };
}

//Here this is perfect, each variable has clear types
const mod1 = moduleFactory({ ref: 0 });
const mod2 = moduleFactory({ otherRef: 123 });

type ComposedModule<C> = Module<C>[];
//Product is composed of multiple modules
type Product<C> = {
  modules: ComposedModule<C>;
};
function productFactory<C>(modulesList: ComposedModule<C>): Product<C> {
  return {
    modules: modulesList,
  };
}

// This should not fire an error and hover on final should list all ModularData that compose the final Product
const final = productFactory([mod1, mod2]);
// I want to be able to see autocompletion here and not fire any errors
final.modules[0].data.ref = 3;
final.modules[1].data.otherRef = 5;

请注意,模块中的数据可以是任何东西。我尝试了很多东西,我找不到解决办法。

谢谢!

【问题讨论】:

  • 请始终将您的代码包含在问题中
  • 那是因为您使用单个泛型类型 C 键入了 ComposedModule,所以一旦从函数调用中推断出 CmodulesList 参数应该只是该类型,但是它在数组中找到其他类型
  • 我可以稍后留下更详细的答案,但我会说问题在这里type ComposedModule&lt;C&gt; = Module&lt;C&gt;[]; 你说数组中的所有模块都具有相同的类型,而不是允许它是具有不同类型的元组。您需要使用泛型来引用整个元组的类型,而不是数据属性的类型。

标签: typescript typescript-typings typescript-generics


【解决方案1】:

您当前的类型设置假定列表中的所有模块都具有相同的数据类型C

type ComposedModule<C> = Module<C>[];

通用的C 可以是这两种类型的联合,但这仍然不允许您访问其中一种类型的.ref 和另一种类型的.otherRef。您真正想要的是知道第一项的数据类型为{ref: number},第二项的数据类型为{otherRef: number}

为此,propertyFactory 函数上的泛型将整个数组描述为一个元组。

我们想要这样的东西:

function productFactory<List>(modulesList: List): {modules: List} {

但我们还想强制List 泛型必须是包含Module 对象的数组。我们使用extends 来做到这一点。

这里的最后一点烦人涉及readonlyas const。默认情况下,您的数组[mod1, mod2] 被解释为任意长度的数组,其元素可以是{ref: number}{otherRef: number}。但是您的最后两行需要更具体的类型。我们需要根据数组索引知道具体的类型。为了将参数解释为类型化元组,您需要使用[mod1, mod2] as const。使用as const 也意味着该函数必须允许readonly 数组。

type Product<List> = { // or <List extends readonly Module<any>[]>
  modules: List;
};

function productFactory<List extends readonly Module<any>[]>(modulesList: List): Product<List> {
  return {
    modules: modulesList,
  };
}

// final has type Product<readonly [Module<{ref: number;}>, Module<{otherRef: number;}>]>
const final = productFactory([mod1, mod2] as const);

// data has type {ref: number}
final.modules[0].data.ref = 3;

// data has type {otherRef: number}
final.modules[1].data.otherRef = 5;

TypeScript Playground Link

【讨论】:

  • 谢谢大家,非常有帮助的解释!
【解决方案2】:

这里有两个问题:

  1. 在这段代码中,您直接插入对象,而不指定您有一个通用接口(类型)。为每个对象创建一个类型,然后创建一个通用类型。
interface A {
  ref: number
}

interface B {
  otherRef: number
}

type AB = A | B;

//Now the error will disappear. Since you omitted the type of your array, typescript inferred the type of the first object
const final = productFactory<AB>([mod1, mod2]);
  1. 当您使用共享接口时,在访问特定于接口的字段之前,您必须验证对象是否属于该接口。为了进行这种类型的评估,我喜欢在可以评估的接口中插入一个公共字段。比如:
interface A {
  type: 'A' //Notice, enums are cleaner to do this kind of things
  ref: number
}

interface B {
  type: 'B'
  otherRef: number
}

const mod1 = moduleFactory({ type: 'A' as 'A', ref: 0 });
const mod2 = moduleFactory({ type: 'B' as 'B', otherRef: 123 });

const final = productFactory<AB>([mod1, mod2]);

//Access ref:
let refObject = final.modules[0].data;
if(refObject.type === 'A'){
 //Now you can access the object
 refObject.ref = 3;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-09
    • 2020-06-29
    • 1970-01-01
    相关资源
    最近更新 更多