自分だけの最強 DI コンテナを作ろう
https://gh-card.dev/repos/rokoucha/penetrate-ts.svg https://github.com/rokoucha/penetrate-ts
TypeScript で DI コンテナを書いた
penetrate-ts と申します
依存が注入可能な形で関数を定義→定義された関数に依存を注入→依存が注入された関数を普通に呼び出す
他の DI コンテナと違い
クラスではなく関数に対して注入
TypeScript 前提でガッチリ型付け、そのかわり実際に渡ってくる依存のチェックはしてない
/tosuke/tosuke.icon に滅茶苦茶助けてもらいました、ありがとう…
なんで書いたの?
クラス書いてまで DI したくねえなと思った
でも関数に1つずつ注入するのダルいね
DI コンテナの1つぐらいならサクっと書けそうだし書いておいて損はないかなと思った
型パズルで無限に時間かかりました
TypeScript の型パズルのお勉強
まだよく分かってない節がある
仕組み
JavaScript 的には…
code:di.js
// コンテナ生成関数を返す関数
() =>
// コンテナの中身を受け取ってコンテナを生成し、依存をコンテナに注入する関数を返す関数
(target) =>
// 依存をコンテナに注入し、実行可能なコンテナを返す関数
(...dependencies) =>
// コンテナを実行する関数
(...args) => target(dependencies, ...args)
TypeScript 的には…
code:di.ts
// 依存の一覧を型として貰っておく
<D extends any[]>() =>
// コンテナに格納する関数を貰う
<F extends PenetrableFunction<D, AnyFunction>>(target: F) =>
// 依存を貰う
(...dependencies: D) =>
// コンテナの依存と実行時の引数を合成してコンテナを実行
(...args: Tail<Parameters<F>>): ReturnType<F> => target(dependencies, ...args)
他に
引数も返り値も any な関数の型
code:anyFunction.ts
type AnyFunction = (...args: any[]) => any
コンテナに格納する関数の型
code:PenetrableFunction.ts
// (...deps, ...args: any[]) => any type PenetrableFunction<D extends any[], F extends AnyFunction> = (
deps: D,
...args: Parameters<F>
) => ReturnType<F>
最初の要素を除いた配列の型
code:tail.ts
/tosuke/tosuke.icon が作ってくれました!
これを一瞬で書けるのが凄いと思う
Conditional Types を使って1つめの要素を any に、その後の要素を Rest にマッチさせて Rest を返す
至極単純ですね
最初は実行時に渡す引数の型と返り値が途中で消えてしまい発狂していた
Generics という概念がよく分かってなかったのでコンテナに入れる関数を AnyFunction にキャストしていた…
依存の型 D extends any[] とコンテナに格納する関数 F extends PenetrableFunction<D, AnyFunction> を同時に貰うのはうまくいかない
依存の型は明示しないといけないけどコンテナに格納する関数は実体を引数で貰うので型を明示する必要がない
D extends any[], F extends AnyFunction みたいな書き方をすると両方明示するか両方とも省略しないといけないっぽい
まだ型パズルよくわからんね~~~~♨
書いてみて
いつ使うのかは不明
こんなよくわからん物を使いたい人は
$ yarn add penetrate-ts して
import {Penetrable} from 'penetrate-ts' すれば使えます
型のテストって書く必要ない?よくわからない
Jest では書けなくない?
extends しまくってるし不安感あるけど型だし要らんか…
コンパイルが通れば OK か?
みんなも DI コンテナ書いてみよう!