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