簡體   English   中英

TypeScript Generics 帶接口參數

[英]TypeScript Generics with interface parameters

我在玩 TypeScript generics 並且有點困惑。

我基本上是在嘗試創建一個接口,該接口具有可以接收任意選項參數的方法。 此參數可以是任何 object。確切的 object 是什么樣子由實現 class 決定。

interface MyOptions {
    foo: string
}

interface TestInterface {
    doSome<T extends Record<string, unknown>>(value: T): void
}

class TestClass implements TestInterface {
    doSome<T = MyOptions>(value: T): void {
        value.foo // complains that foo doesn't exist
    }
}

一切看起來都很好,但是當我嘗試訪問value.foo時,似乎沒有輸入value

難道我做錯了什么?

更新

我發現了一些關於不擴展Record<string, unknown>的接口的有用信息,說使用type代替(參見接口 object couldn't extends Record<string, unknown> )。

但是,如下所示更新上面的代碼段后,問題仍然存在。

type MyOptions = {
    foo: string
}

interface TestInterface {
    doSome<T extends Record<string, unknown>>(value: T): void
}

class TestClass implements TestInterface {
    doSome<T = MyOptions>(value: T): void {
        value.foo // complains that foo doesn't exist
    }
}

您不能以這種方式使 function 過載。 如果您嘗試為此創建聯合,則可以使用以下代碼示例:

type MyOptions = Record<string, unknown> & { foo: string };

interface TestInterface {
  doSome<T extends MyOptions>(value: T): void;
}

class TestClass implements TestInterface {
  doSome<T extends MyOptions>(value: T): void {
    console.log(value.foo);
  }
}

const test = new TestClass();

test.doSome({ foo: "bar", baz: "qux" });

/*
*  The combined type of the two objects is:
* TestClass.doSome<{
    foo: string;
    baz: string;
}>(     value: {foo: string, baz: string}): void
* 
* */

用於超載鏈接的注意事項的有用鏈接

更新

我相信我明白你在找什么; 您遇到的問題是 Typescript 不知道您的 function 或 class 中 T 的哪些屬性。編譯器無法證明foo屬性適用於每種類型。 您有兩個選擇,要么向參數添加基本類型,要么通過extends關鍵字應用通用約束:

type MyOptions = {
  foo: string;
};

type MyOtherOptions = {
  bar: string;
};

interface TestInterface {
  doSome(value: unknown): void;
}

class TestClass<T extends Record<string, unknown>> implements TestInterface {
  doSome = (value: T): void => {
    console.log(value.foo);
  };
}

const test = new TestClass<MyOtherOptions>();
test.doSome({ bar: "baz" });

const test2 = new TestClass<MyOptions>();
test2.doSome({ foo: "bar" });

泛型調用簽名和泛型類型之間有一個重要的區別。 通用調用簽名在調用簽名本身上具有通用類型參數,如下所示:

interface TestInterfaceOrig {
    doSome<T extends object>(value: T): void
}

當你有一個通用的調用簽名時,調用者可以指定類型參數:

declare const testInterfaceOrig: TestInterfaceOrig;
testInterfaceOrig.doSome<{ a: string, b: number }>({ a: "", b: 0 });
testInterfaceOrig.doSome({ a: "", b: 0 });

在上面,調用者可以在兩次調用中都選擇T{a: string, b: number} 在第二次調用中,編譯器從value參數中推斷出該類型,但它是相同的……調用者負責。 另一方面,實現者必須編寫doSome()以便它適用於調用者選擇的任何T

const testInterfaceOrig: TestInterfaceOrig = {
    doSome<T extends object>(value: T) {
        console.log(value); // 🤷‍♂️ there's not much I can do, 
        // all I know is that value is an object
    }
}

選擇類型參數 default對實現者沒有任何幫助或改變,因為默認值不以任何方式限制輸入。

const testInterfaceOrig: TestInterfaceOrig = {
    doSome<T extends object = MyOptions>(value: T) {
        console.log(value); // 🤷‍♂️ still the same
    }
}

如果實現者試圖實際約束輸入,則會出現錯誤......調用者選擇,而不是實現者:

const testInterfaceOrigNope: TestInterfaceOrig = {
    doSome<T extends MyOptions>(value: T) { // error!
        console.log(value.foo);
    }
}

這不是你要找的,所以你不能這樣寫。


另一方面,泛型類型將泛型類型參數作為類型聲明的一部分,如下所示:

interface TestInterface<T extends object> {
    doSome(value: T): void
}

在這里,本身沒有TestInterface類型(盡管您也可以在這里設置默認值,但我們不要跑題)。 您不能擁有TestInterface類型的值。 相反,對於某些 object 類型T ,您可以擁有類型TestInterface<{a: string, b: number}>TestInterface<MyOptions>TestInterface<T>的值。 一旦您擁有該類型的值, doSome()方法就只能接受T類型的值。 調用簽名不是通用的; 調用者不能在這里選擇T T的選擇由提供TestInterface<T>實例的人做出:

class TestClass implements TestInterface<MyOptions> {
    doSome(value: MyOptions): void {
        value.foo
    }
}

所以這里TestClass實現了TestInterface<MyOptions> doSome()方法只接受MyOptions 這意味着編譯器知道該value具有foo屬性。 因此,調用者根本不負責T ,正如所希望的那樣:

const tc = new TestClass();
tc.doSome({ foo: "abc" }); // okay
tc.doSome({ a: "", b: 0 }); // error, that's not a MyOptions.
tc.doSome<{ a: string, b: number }>({ a: "", b: 0 }); // error, doSome is not generic

游樂場代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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