TypeScriptの2つの型引数を取るGeneric関数で、1つのみを必ず指定させる
こういうことをしたい
関数を定義しようとしている
この関数は、2つの型引数が必要なgenericな型を持つ
片方の型は、関数の利用者に指定させるのを強制したい
もう片方の型は、引数より自明に導かれるので指定させる必要はない
寧ろ、使い勝手の良さを考えれば、指定させないほうが良い
具体例
sqlと、そのparameterを引数にとって、SQLの実行結果を返す関数を考える
code:ts
async function get(query, params?) {
...
return result;
}
try/catchしてないとかそういう細かいことはここでは関係ないので端折っているmrsekut.icon
引数部分の型は、
queryの型をQとした時に、
params?: QueryParameters<Q>
のように指定したい
返り値の型は、推論不可能なので、利用者が明示的に指定した型Rにしたい
安全ではないが、そういう仕様だとする
利用時にはこんな風に使って欲しい
code:ts
const result = await get<User>(SELECT id FROM users WHERE id = :id, { id: userId })
こんな感じで、「返り値はUserだよ」という部分は利用者に指定して欲しい
TypeScriptは、Genericsに2つの引数を取った時、利用者には
両方の型を指定する
1つも型を指定しない
のいずれかしか選択できない
つまり、片方だけ明示的に書いてもらうということができない
解決策は2つ
返り値がunknownになるように愚直に書く
カリー化する
返り値がunknownになるように愚直に書く
code:ts
async function get<R extends unknown, Q extends string>(
query: Q,
params?: QueryParameters<Q>
): Promise<R> {
...
return result;
}
返り値Rをunknownにしておけばいい
利用者はこうする
code:ts
const result: User = await get(SELECT id FROM users WHERE id = :id, { id: userId })
getの返り値はunknownになるので、何かしらの型を指定しないと、以降の実装で型エラーが起きる
よって、利用者に1つの型の指定を強制できる
カリー化する
code:ts
const get =
<R extends unknown[]>() =>
async <Q extends string>(query: Q, params?: QueryParameters<Q>): Promise<R> => {
...
return result;
};
利用者はこうする
code:ts
const result = await get<User>()(SELECT id FROM users WHERE id = :id, { id: userId })
例としては、この記事のほうが適切かもしれない
ただし、この記事は2018年から更新されていないので記事の要旨に関しては今はもっと簡潔な書き方があるように思う
今回の例に関しては前者のほうが良いと思うmrsekut.icon
後者のほうが適している例もあると思う