[英]Generic type of class method parameter type
我在嘗試正確鍵入 class 中的方法時遇到一些問題。首先,我有這個 class:
export class Plugin {
configure(config: AppConfig) {}
beforeLaunch(config: AppConfig) {}
afterSetup(runtime: Runtime) {}
afterStartup(runtime: Runtime) {}
onUserError(error: UserError) {}
}
另一個 class 以編程方式運行它的一些方法:
export class PluginManager {
_plugins: Plugin[];
_pythonPlugins: any[];
constructor() {
this._plugins = [];
this._pythonPlugins = [];
}
private setUpPlugins = (property: keyof Plugin, parameter:AppConfig | Runtime | UserError ) => {
for (const p of this._plugins) p[property](parameter); // <- parameter is erroring out
for (const p of this._pythonPlugins) p[property]?.(parameter);
}
問題是 setUpPlugins 中第一個編程調用中的參數在抱怨:
Argument of type 'AppConfig | Runtime | UserError' is not assignable to parameter of type 'AppConfig & Runtime & UserError'.
Type 'AppConfig' is not assignable to type 'AppConfig & Runtime & UserError'.
Type 'AppConfig' is missing the following properties from type 'Runtime': config, src, interpreter, globals, and 8 more. (tsserver 2345)
我不知道為什么它期望的類型是交集而不是聯合。 無論如何,我嘗試使用 generics 解決此問題:
private setUpPlugins = <T extends keyof Plugin, U extends Parameters<Plugin[T]>> (property: T, parameter:U ) => {
for (const p of this._plugins) p[property](parameter);
for (const p of this._pythonPlugins) p[property]?.(parameter);
}
但現在參數抱怨一個新錯誤:
Argument of type '[config: AppConfig] | [config: AppConfig] | [runtime: Runtime] | [runtime: Runtime] | [error: UserError]' is not assignable to parameter of type 'AppConfig & Runtime & UserError'.
Type '[config: AppConfig]' is not assignable to type 'AppConfig & Runtime & UserError'.
Type '[config: AppConfig]' is not assignable to type 'Runtime'. (tsserver 2345)
解決這個問題的正確方法是什么? 我試圖避免使用任何。
喜歡的版本
private setUpPlugins = (
property: keyof Plugin, parameter: AppConfig | Runtime | UserError
) => {
for (const p of this._plugins) p[property](parameter); // error!
// Argument of type 'AppConfig | Runtime | UserError' is not assignable to
// parameter of type 'AppConfig & Runtime & UserError'.
}
有充分理由失敗; 如果property
具有聯合類型keyof Plugin
並且parameter
具有聯合類型AppConfig | Runtime | UserError
AppConfig | Runtime | UserError
AppConfig | Runtime | UserError
,沒有什么可以阻止某人使用不匹配的 arguments 調用setupPlugins()
。這些聯合是不相關的; 沒有什么能確保正確的鍵與正確的值相伴。
如文檔中所述,使用編譯器知道安全的參數調用函數聯合(如p[property]
的唯一方法是使用參數類型的交集。 如果parameter
是AppConfig & Runtime & UserError
類型,那么無論property
是什么,調用p[property](parameter)
都是安全的。 但是AppConfig | Runtime | UserError
AppConfig | Runtime | UserError
AppConfig | Runtime | UserError
不是那種類型,所以允許調用是不安全的。
理想情況下,您會限制property
和parameter
正確關聯。 這可以用多種方式表示。 例如,這是您嘗試使用generics的(稍微固定的)版本:
private setUpPlugins = <K extends keyof Plugin>(
property: K, parameter: Parameters<Plugin[K]>[0]
) => {
for (const p of this._plugins) p[property](parameter); // error!
// Argument of type 'AppConfig | Runtime | UserError' is not
// assignable to parameter of type 'AppConfig & Runtime & UserError'.
}
但是正如您所看到的,您會遇到同樣的錯誤。 這里不太可能傳入不匹配的術語,但編譯器無法遵循邏輯。 它仍然像property
和parameter
一樣是不相關的聯合類型。
TypeScript 對所謂的相關聯合類型沒有很好的支持。 這是microsoft/TypeScript#30581的主題,在最長的時間里,我知道最好的做法是使用一些類型斷言並繼續。
然而,現在我們有microsoft/TypeScript#47109中描述的方法:將類型重構為“基本”映射接口,該接口清楚地表示我們關心的相關性。 然后根據分布式 object 類型執行所有操作,其中我們map 在映射接口上並立即索引到它以獲得相關聯合,如果我們需要的話。 這通常比人們認為應該做的工作要多,但它的優點是編譯器實際上可以遵循它。
根據您的示例代碼,重構可能如下所示:
interface PluginParam {
configure: AppConfig,
beforeLaunch: AppConfig,
afterSetup: Runtime,
afterStartup: Runtime,
onUserError: UserError
}
type PluginMethods = { [K in keyof PluginParam]: (arg: PluginParam[K]) => void }
PluginMethods
類型,如果你檢查它,看起來很像你的Plugins
class,這是有意的。 如果Plugins
是一個接口而不是 class,我們可以只使用PluginMethods
。 相反,我們需要保留 class。為了確保我們仍然獲得類型安全,我們可以使用一個implements
子句:
export class Plugin implements PluginMethods { // okay
configure(config: AppConfig) { }
beforeLaunch(config: AppConfig) { }
afterSetup(runtime: Runtime) { }
afterStartup(runtime: Runtime) { }
onUserError(error: UserError) { }
}
現在是方法:
private setUpPlugins = <K extends keyof PluginParam>(
property: K, parameter: PluginParam[K]
) => {
const _plugins: PluginMethods[] = this._plugins;
for (const p of _plugins) p[property](parameter); // okay
}
我們將一個新的_plugins
變量注釋為PluginMethods[]
類型並將this._plugins
分配給它。 這似乎毫無意義,但它是為了讓編譯器理解this._plugins
可以被視為PluginMethods
, PluginParam
上的映射類型。 然后,當我們調用p[property](parameter)
時,一切正常。 p[property]
被視為(arg: PluginParam[K]) => void
類型,並且parameter
被視為PluginParam[K]
類型。 所以 function 不是 function 類型的聯合,它是一個通用的 function,其參數與parameter
的類型相同。 因此它編譯沒有錯誤。
您會注意到,如果您更改其中任何一項(例如,取消const _plugins
的注釋),錯誤將重新出現; 編譯器將丟失線程,您將回到開始的地方。 只有將所有內容顯式表示為基本映射接口的通用映射版本,我們才能讓編譯器盡可能安全地驗證我們正在做的事情。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.