Typescript 是否使用参数或泛型来约束另一个参数的类型?

Typescript 是否使用参数或泛型来约束另一个参数的类型?,typescript,type-inference,typescript-generics,Typescript,Type Inference,Typescript Generics,我想学习如何更有效地使用泛型,因此我想尝试重构一段当前冗长且重复的代码 目前我有: interface FooData { foo: string; } function renderFoo (data: FooData): string { return templateEngine.render("./foo.template", data) } interface SpamData { spam: number; } function renderSpam (data:

我想学习如何更有效地使用泛型,因此我想尝试重构一段当前冗长且重复的代码

目前我有:

interface FooData {
  foo: string;
}

function renderFoo (data: FooData): string {
  return templateEngine.render("./foo.template", data)
}

interface SpamData {
  spam: number;
}

function renderSpam (data: SpamData): string {
  return templateEngine.render("./spam.template", data)
}

调用
templateEngine.render
将模板路径和数据简单地结合在一起,而无需进行类型检查,我希望在此基础上构建类型安全性

上述代码起作用,并确保例如
spam.template
仅使用类型为
SpamData
的数据呈现,但结构冗长且重复

我认为可能有一种解决方案,它提供了一个可调用的单一函数(例如,
renderTemplate
),该函数具有一个签名(以某种方式?)来根据所选模板强制执行
数据的形状。但我是个新手,不知道我在问什么,也不知道该怎么做


我的问题是:如何重构?如果听起来我基本上是在找错方向,我也愿意接受广泛的反馈,你的想法是值得赞赏的。

你应该将
FooData | SpamData
转换为带有
种类的
模板
判别属性,或者你应该将两个参数传递给
renderTemplate
,第一个类似于
种类
模板
字符串。在这两种情况下,您都应该选择一些字符串文字来区分数据类型。我将在这里使用
“foo”
“spam”
。首先,受歧视的工会:

  interface FooData {
    kind: "foo";
    foo: string;
  }

  interface SpamData {
    kind: "spam";
    spam: number;
  }

  type Data = FooData | SpamData;

  function render(data: Data): string {
    return templateEngine.render("./" + data.kind + ".template", data);
  }

  render({ kind: "foo", foo: "hey" }); // okay
  render({ kind: "spam", spam: 123 }); // okay
  render({ kind: "foo", spam: 999 }); // error!
您可以看到,
Data
FooData
SpamData
的一个联合体,每个联合体都有一个
kind
属性,可以用来区分它是哪种类型。您可以使用字符串操作构建模板路径是很偶然的,但是如果这对您不起作用,您可以设置一个查找表

双参数方法如下所示:

  interface FooData {
    foo: string;
  }

  interface SpamData {
    spam: number;
  }

  interface DataMap {
    foo: FooData;
    spam: SpamData;
  }

  function render<K extends keyof DataMap>(kind: K, data: DataMap[K]): string {
    return templateEngine.render("./" + kind + ".template", data);
  }

  render("foo", { foo: "hey" }); // okay
  render("spam", { spam: 123 }); // okay
  render("foo", { spam: 999 }); // error!
接口数据{
foo:string;
}
接口SpamData{
垃圾邮件:数字;
}
接口数据映射{
foo:FooData;
spam:SpamData;
}
函数呈现(种类:K,数据:DataMap[K]):字符串{
返回templateEngine.render(“./”+kind+“.template”,数据);
}
渲染(“foo”,{foo:hey});//可以
呈现(“垃圾邮件”,{spam:123});//可以
呈现(“foo”,{spam:999});//错误!
在这里,我们提供了一个名为
DataMap
的映射接口,它表示
kind
字符串和数据类型之间的关系。它类似于区分并集,不过我使用了一个函数来捕获
render()
参数之间的约束。关于字符串操作与实际调用
templateEngine.render()
的查找的相同点也在这里


希望这能给你一些想法。祝你好运


首先,让我说我不确定重构它是否有意义。尤其是模板是一个文件路径。对于TypeScript
/foo.template
foo.template
是不同的,而对于模板引擎,它们可能是相同的。然而,我将留给您决定是要重构还是保持原样

以下是我针对这个问题的两个解决方案:

函数重载 允许您指定替代方法签名,我们可以在其中指定模板和数据接口的组合:

函数renderTemplate(模板:'./foo.template',数据:FooData):字符串;
函数renderTemplate(模板:'./spam.template',数据:SpamData):字符串;
函数renderTemplate(模板:string,数据:any):string{
返回templateEngine.render(模板,数据);
}
renderTemplate(“./unknown.template”,{});//错误
renderTemplate(“./foo.template”,{spam:42});//错误
renderTemplate(“./foo.template”,{foo:'bar});//无误

仿制药 或者,我们可以利用泛型来完成同样的任务。读起来有点困难,但比函数重载要简单

首先,我们需要在模板名称和数据接口之间进行某种映射。为此,我们将使用一个新接口:

接口模板映射{
“/foo.template”:FooData,
“/spam.template”:SpamData
}
现在,对于函数,我们为
template
参数添加了一个通用参数
T
,该参数被限制为
TemplateMap
中的属性名称。我们通过指定TemplateMap的
T扩展键来实现这一点。最后,
data
参数需要匹配
TemplateMap
中相应的类型。我们将使用
TemplateMap[T]
检索此类型

函数renderTemplate(模板:T,数据:TemplateMap[T]):字符串{
返回templateEngine.render(模板,数据);
}
renderTemplate(“./unknown.template”,{});//错误
renderTemplate(“./foo.template”,{spam:42});//错误
renderTemplate(“./foo.template”,{foo:'bar});//无误

在您的例子中,困难在于我们必须在函数中传递模板的路径,这并不方便(如@lukasgeiter所示)

@jcalz提出了两个很好的解决方案,但要注意以下几点:

  • 歧视性结合是一种性感的模式,但并不总是有用的。在本例中,它是一种数据类型,但假设该数据来自服务器,
    kind
    discrimate属性可能不存在

  • 字符串操作不安全,我建议使用map
    {[K in Kind]:TemplatePath;}
    。字符串串联按定义类型是不安全的,使用非普通路径时可能会出现错误,调试时间可能更长。通过使用映射,您可以将可能的bug源集中到一个常数,这是一个更易于维护的常数

我的守则建议:

接口数据{
foo:string;
}
接口SpamData{
垃圾邮件:数字;
}
接口模板映射{
foo:FooData;
垃圾邮件:SpamDa