重要事項説明
型に不慣れなため、誤った情報を記載している可能性があります.
すぐに直しますので、コメント / 修正リクエストお待ちしています
要約
// NG
// jqueryのonが要求する型をハンドラ関数が満たしていない
$('input[type="file"]').on('change', (ev: Event) => {
// do something
})
// OK
// ハンドラの引数の型をjqueryに合わせれば、onの要求する型を満たしたことになる
$('input[type="file"]').on('change', (ev:JQuery.Event) => {
// do something
})
たったこれだけの修正ですが、これを解決する過程で、オーバーロードとインターフェースの読み方を少しずつ追ったのでそれもまとめたいと思います。
詳細
jqueryのonをtypescriptで書こうとすると意外に躓くことがあります。
$('input[type="file"]').on('change', (ev:Event) => {
// do something
})
こう書くと次のエラーが出ます
RROR in /path/to/issus/issus/app/assets/javascripts/commons/input_file.ts(4,30)
TS2345: Argument of type '"change"' is not assignable to parameter of type
'PlainObject<false | EventHandler<HTMLElement, (ev: Event) => void> | EventHandlerBase<any, Event<...'.
change
が引数の型に合わないとのこと。
(IntelliJのcmd + クリックで)onの引数の型を見てみると、次のようにあり、オーバーロードされていることが分かります。
(オーバーロードの説明はしません)
interface JQueryStatic<TElement extends Node = HTMLElement> {
// 省略
on<TData>(events: string,
selector: JQuery.Selector | null,
data: TData,
handler: JQuery.EventHandler<TElement, TData> | JQuery.EventHandlerBase<any, JQuery.Event<TElement, TData>>): this;
on(events: string,
selector: JQuery.Selector,
handler: JQuery.EventHandler<TElement> | JQuery.EventHandlerBase<any, JQuery.Event<TElement>> | false): this;
on<TData>(events: string,
data: TData,
handler: JQuery.EventHandler<TElement, TData> | JQuery.EventHandlerBase<any, JQuery.Event<TElement, TData>>): this;
// これが適用されると期待しつつなぜか適用されない
on(events: string,
handler: JQuery.EventHandler<TElement> | JQuery.EventHandlerBase<any, JQuery.Event<TElement>> | false): this;
on<TData>(events: JQuery.PlainObject<JQuery.EventHandler<TElement, TData> | JQuery.EventHandlerBase<any, JQuery.Event<TElement, TData>> | false>,
selector: JQuery.Selector | null,
data: TData): this;
on(events: JQuery.PlainObject<JQuery.EventHandler<TElement> | JQuery.EventHandlerBase<any, JQuery.Event<TElement>> | false>,
selector: JQuery.Selector): this; // tslint:disable-line:unified-signatures
on<TData>(events: JQuery.PlainObject<JQuery.EventHandler<TElement, TData> | JQuery.EventHandlerBase<any, JQuery.Event<TElement, TData>> | false>,
data: TData): this;
on(events: JQuery.PlainObject<JQuery.EventHandler<TElement> | JQuery.EventHandlerBase<any, JQuery.Event<TElement>> | false>): this;
// 省略
}
なんでやと考え、第一引数の"string"
が'PlainObject<false | EventHandler<HTMLElement, (ev: Event) => void> | EventHandlerBase<any, Event<...'
に該当するオーバーロードが適用されている様子。
- 引数が2つで
- 第一引数の型が上記のものであり、
- 第二引数が関数になりうるもの
を探すと、
on<TData>(events: JQuery.PlainObject<JQuery.EventHandler<TElement, TData> | JQuery.EventHandlerBase<any, JQuery.Event<TElement, TData>> | false>,
data: TData): this;
これだとわかります。
TData
に何も指定していないために、第二引数のdataの型がanyになり、関数が受け入れられてしまい、このオーバーロードが適用されている様子。
試しに
// {hoge: false}はJQuery.PlainObjectを最低限満たす形にしただけで特に意味はありません
// TDataにstringを指定し、第二引数に関数を渡せないようにしてみる
$('input[type="file"]').on<string>({hoge: false}, (ev:Event) => {})
とかくと、
Argument of type '(ev: Event) => void' is not assignable to parameter of type 'string'.
と、期待通りのエラー「第二引数に関数が渡ってきているけど、TDataで指定したstringに合わないよ」が返されます。
ということで期待通り動かすには第二引数のハンドラの型を、求められている JQuery.EventHandler<TElement> | JQuery.EventHandlerBase<any, JQuery.Event<TElement>> | false
に合わせれば良さそうなのでそのようにしてあげる方針をたてます。
JQuery.EventHandlerBase<any, JQuery.Event<TElement>>
を覗くと、
interface EventHandlerBase<TContext extends object, T> {
// Extra parameters can be passed from trigger()
(this: TContext, t: T, ...args: any[]): void | false | any;
}
とあるので、
(this: any, t: JQuery.Event<TElement>, ...args: any[]): void | false | any;
を満たす関数にすればよいとわかります。
anyは一旦無視すると、つまるところJQuery.Event型の引数を持てばいいとわかるので、
$('input[type="file"]').on('change', (ev:JQuery.Event) => {
// do something
})
こうしてあげれば動く!という話でした。