如果這是重復的,請提前道歉,但我的搜索沒有找到任何非常適合我遇到的問題的東西。
首先,期望的行為是有一個具有兩個引數的類方法,第二個是可選的,其中第二個引數的型別取決于第一個引數的型別。如果第一個引數是 A 型別,則第二個引數應該始終是必需的并且應該是 X 型別,如果第一個引數是 B 型別,則應該省略第二個引數。
我已經通過函式多載實作了類似的東西:
// types
enum MessageType { FOO, BAR, BAZ }
type MessagePayload<T extends MessageType> = T extends MessageType.FOO
? string
: T extends MessageType.BAR
? number
: never;
// overloads
function sendMessage<T extends MessageType.BAZ>(action: T): void
function sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
// implementation
function sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
// tests
sendMessage(MessageType.FOO, "10") // no error - as expected
sendMessage(MessageType.FOO, 10) // error - as expected, payload is not string
sendMessage(MessageType.FOO) // error - as expected, payload must be string
sendMessage(MessageType.BAZ); // no error - as expected - since MessageType is BAZ
但是,當應用于類方法時,完全相同的構造不會產生相同的結果。此片段是第一個片段的延續,并使用相同的型別:
// interface
interface ISomeClient {
sendMessage<T extends MessageType.BAZ>(action: T): void
sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
}
// implementation
class SomeClient implements ISomeClient {
sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
}
// tests
const client = new SomeClient();
client.sendMessage(MessageType.FOO, "10"); // no error - as expected
client.sendMessage(MessageType.FOO, 10); // error, payload is not string
client.sendMessage(MessageType.FOO) // no error??? different behavior than function example
client.sendMessage(MessageType.BAZ); // this part works fine
這是TS Playgound上更完整的示例。
所以,我想這是一個兩部分:
- 為什么這不適用于類示例?
- 有沒有更好的方法來實作這一點,它適用于類和函式,并且不需要維護多載來捕獲不需要有效負載的型別?我在這里使用了列舉和條件型別來約束第二個引數以匹配給定第一個引數的預期。我已經嘗試過另一種涉及鍵入 map 的鍵的方法,但它看起來很笨拙,仍然需要多載,并且對于類和函式也存在同樣的問題。
謝謝。
uj5u.com熱心網友回復:
1.為什么這不適用于課堂示例?
我認為問題在于,class沒有像第三個獨立函式簽名那樣將簽名僅視為實作簽名,因為多載是單獨宣告的。因此,class增加了這些,添加了第三個公共簽名,與第三個簽名不公開的函式多載相反,它只是實作簽名。
您可以通過不將多載(僅)放在介面宣告中來修復它。要么不使用介面:
class SomeClient {
sendMessage<T extends MessageType.QAT | MessageType.QAZ>(action: T): void;
sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void;
sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
}
游樂場示例
...或者確實使用介面,但也要重復class構造中的多載,以便 TypeScript 知道第三個是實作簽名:
interface ISomeClient {
sendMessage<T extends MessageType.QAT | MessageType.QAZ>(action: T): void
sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
}
class SomeClient implements ISomeClient {
sendMessage<T extends MessageType.QAT | MessageType.QAZ>(action: T): void
sendMessage<T extends MessageType>(action: T, payload: MessagePayload<T>): void
sendMessage<T extends MessageType>(action: T, payload?: MessagePayload<T>) {
// do something
}
}
游樂場示例
這是重復的,但我不確定除了分配給SomeClient.prototype事后之外還有其他方法。
2.有沒有更好的方法來實作這個...
我傾向于為此喜歡函式多載,但確實它們并不適用于所有事情,如果你有很多這些,它會很快變得笨拙。
我應該注意,我仍然處于 TypeScript 的熟練水平,所以可能還有其他選擇,但我可以想到兩種選擇:
使用帶有可變元組型別的 rest 引數
使用有區別的聯合,所以總是只有一個引數
在我發現函式多載太麻煩的地方,我傾向于區分聯合,但元組的想法有點可愛,所以我想我會包括它。
休息 元組
而不是MessagePayload<T>,您MessageParams<T>定義了一個基于 的元組T:
type MessageParams<T extends MessageType> = T extends MessageType.FOO
? [T, string]
: T extends MessageType.BAR
? [T, number]
: T extends MessageType.BAZ
? [T, User]
: [T];
(如果MessagePayload<T>因為其他原因需要,可以從上面推匯出來:type MessagePayload2<T extends MessageType> = MessageParams<T>[1];。)
然后該方法將其用作休息引數的型別:
class SomeClient {
sendMessage<T extends MessageType>(...args: MessageParams<T>) {
const action = args[0];
// do something
}
}
游樂場示例
不過,開發人員的體驗非常類似于多載。
歧視聯盟
最后一個選項是一個更大的變化:你根本沒有單獨的引數,只有一個物件型別,它是一個可區分的聯合:
type FOOMessage = {action: MessageType.FOO; payload: string;};
type BARMessage = {action: MessageType.BAR; payload: number;};
type BAZMessage = {action: MessageType.BAZ; payload: User;};
type OtherMessage = {action: Exclude<MessageType, MessageType.FOO | MessageType.BAR | MessageType.BAZ>;};
// `OtherMessage` is the catch-all for all message types other than the
// ones with their own interface, note the use of `Exclude`
type Message = FOOMessage | BARMessage | BAZMessage | OtherMessage;
// ...
class SomeClient {
sendMessage(message: Message) {
const action = message.action;
// do something
}
}
對它的呼叫更改為傳遞一個物件:
// tests
client.sendMessage({action: MessageType.FOO, payload: "string"});
client.sendMessage({action: MessageType.FOO}); // Error as desired
client.sendMessage({action: MessageType.QAT});
游樂場示例
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/460209.html
上一篇:相同通配符的下限和上限通用約束
