![](/img/trans.png)
[英]TypeScript: How to constrain a generic method's return type to depend on argument type?
[英]Use an argument or generic to constrain type of another argument?
我想學習如何更有效地使用泛型,因此想嘗試重構一段當前冗長和重復的代碼。
目前我有這個:
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
),該函數具有一個簽名(以某種方式?)根據所選模板強制執行data
形狀。 但是我太新了類型,無法理解我在問什么或者確實如何去做。
我的問題是:這怎么可能被重構? 我也願意接受廣泛的反饋,如果它聽起來像是從根本上吠叫錯誤的樹,你的想法是值得贊賞的。
你應該轉FooData | SpamData
FooData | SpamData
與具有kind
或template
判別屬性的區別聯合 , 或者您應該向renderTemplate
傳遞兩個參數,第一個是kind
或template
字符串。 在任何一種情況下,您都應該選擇一些字符串文字來區分數據類型。 我會在這里使用"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
和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!
在這里,我們提出了一個名為DataMap
的映射接口,它表示kind
字符串和數據類型之間的關系。 雖然我使用泛型函數來捕獲render()
參數之間的約束,但它類似於區分聯合。 關於實際調用templateEngine.render()
string-manipulation-vs-lookup的相同觀點也在這里。
希望能給你一些想法。 祝好運!
首先,讓我說我不確定重構這個是否合理。 特別是因為模板是文件路徑。 對於TypeScript ./foo.template
和foo.template
是不同的,而對於模板引擎,它們可能是相同的。 但是,我會留給您決定是否要重構或保持原樣。
以下是此問題的兩個解決方案:
函數重載允許您指定備用方法簽名,我們可以在其中指定模板和數據接口的組合:
function renderTemplate(template: './foo.template', data: FooData): string;
function renderTemplate(template: './spam.template', data: SpamData): string;
function renderTemplate(template: string, data: any): string {
return templateEngine.render(template, data);
}
renderTemplate("./unknown.template", {}); // error
renderTemplate("./foo.template", { spam: 42 }); // error
renderTemplate("./foo.template", { foo: 'bar' }); // no error
或者,我們可以利用泛型和查找類型來完成相同的操作。 它有點難以閱讀,但比函數重載更簡潔。
首先,我們需要在模板名稱和數據接口之間進行某種映射。 為此我們將使用一個新的界面:
interface TemplateMap {
"./foo.template": FooData,
"./spam.template": SpamData
}
現在,對於函數,我們為template
參數添加一個通用參數T
,該參數被約束為TemplateMap
屬性名稱。 我們通過指定T extends keyof TemplateMap
來完成此T extends keyof TemplateMap
。 最后, data
參數需要匹配TemplateMap
中的相應類型。 我們將使用TemplateMap[T]
檢索此類型。
function renderTemplate<T extends keyof TemplateMap>(template: T, data: TemplateMap[T]): string {
return templateEngine.render(template, data);
}
renderTemplate("./unknown.template", {}); // error
renderTemplate("./foo.template", { spam: 42 }); // error
renderTemplate("./foo.template", { foo: 'bar' }); // no error
你的情況很困難,我們必須在函數中傳遞模板的路徑,這不是很方便(如@lukasgeiter所示)。
@jcalz提出了兩個很好的解決方案,但要注意一些要點:
歧視聯盟是一種性感的模式,但並不總是可用。 在這種情況下,它處於數據類型,但想象這些數據來自服務器,可能無法存在kind
discrimate屬性;
字符串操作不安全,我建議使用地圖{ [K in Kind]: TemplatePath; }
{ [K in Kind]: TemplatePath; }
。 根據定義,字符串連接類型不安全,使用非普通路徑時可能會出錯,並且調試可能會更長。 通過使用地圖,您可以將可能的錯誤源集中到一個常量,更具有可維護性。
我的代碼建議:
interface FooData {
foo: string;
}
interface SpamData {
spam: number;
}
interface TemplateMap {
foo: FooData;
spam: SpamData;
}
type Kind = keyof TemplateMap;
const templateKindMap: Readonly<{ [K in Kind]: string }> = {
foo: './foo.template',
spam: './spam.template'
};
function render<K extends Kind>(kind: K, data: TemplateMap[K]): string {
return templateEngine.render(templateKindMap[kind], data);
}
render('foo', {foo: ''});
render('spam', {spam: 0});
希望它有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.