[英]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.