簡體   English   中英

使用參數或泛型來約束另一個參數的類型?

[英]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與具有kindtemplate判別屬性的區別聯合或者您應該向renderTemplate傳遞兩個參數,第一個是kindtemplate字符串。 在任何一種情況下,您都應該選擇一些字符串文字來區分數據類型。 我會在這里使用"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!

您可以看到DataFooDataFooDataSpamData ,每個都有一個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.templatefoo.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM