関数は、その引数の型が取りうる値を全てサポートする責任がある
例えば、3つのboolean型を引数に取る関数があった場合
code:ts
const f = (a: boolean, b: boolean, c: boolean) => {..}
booleanのinhabitantは2つなので、この関数fは、$ 2^3パターン全てを正常に処理する責務がある
どんな組み合わせの3つが来たとしても処理を全うしないといけない
numberやstringだともっとパターン数が増える
numberが引数の場合、
負数も
0も
小数も
整数も
正数も
でけー数も
いずれが来ても正常に処理する必要がある
stringが引数の場合、
空文字も
1文字も、
なげー文字も
いずれが来ても正常に処理する必要がある
recordの場合も同様で、
code:1.ts
type X = {
a: A1 | A2;
b: B1 | B2;
}
const f = (x: X) => {..}
仮に、A1,A2,B1,B2のinhabitantがどれも1つだとすると、
以下の4パターンを正常に処理する必要がある
{ a: A1, b: B1}
{ a: A1, b: B2}
{ a: A2, b: B1}
{ a: A2, b: B2}
引数の個数が増えれば増えるほど、
型が荒くなれば荒くなるほど、
サポートしないといけない組み合わせが増える
関数を定義する際に考えるべきこと
そもそも、その引数は全て必要なものか?
処理には不要なものが紛れ込んでいないか?
関数の引数にEntityを丸ごと取るのと、fieldごとに取ることの差異
型を絞って許容するものを制限する
本当に数値全てを取るのか?
1|2|3で十分だったりはしないか
本当に文字列すべてを取るのか?
\`id${number}\`で表現できたりしないか
1.tsは本当に4パターンなのか?
本当に表現したかったのはこの2パターンではないのか?
code:2.ts
type X = { a: A1; b: B1 }
| { a: A2; b: B2 }
const f = (x: X) => {..}
でも、厳密にやりすぎるとそれはそれで大変になるので妥協が必要mrsekut.icon
どれだけ楽できるかは、その言語が持っている型システムの力にも依る
簡単な例で言えば、numberが、IntegerとFloatに分かれているとかも1つ
動的型付き言語の場合、「どんな値が来ても大丈夫」になっている必要がある
というかそもそもそういう思想なので、逆に言えば実装者は考えなくても良い(そんなことはないが)
だからどんな値が来てもクラッシュせずに、なんか良い感じのエラーで処理される
それが開発者の意図に沿うものかどうか知らんけど
動的型付き言語は許容する
React Componentでの例 ref
このとき、LoginButtonが対応しなければならないパターンは4パターンに増えます。例えば「colorが"red"でhasShadowがtrueのパターンは存在しないから実装をサボろう」のような考え方をしてはいけません
わかるmrsekut.icon
Atomic Designのatomsはやや例外になる
いや別に「例外」ではないかmrsekut.icon
https://zenn.dev/takepepe/articles/atoms-type-definitions
これを読んで思ったmrsekut.icon
atomsがやりたいことが、propsの制限であるのならば、↑の記事の「お勧めできません」の書き方で書くべき
でも実際、atomsの目的はそこではなく、統一的なstyleのあたったパーツを使いたい、というものなので、propsは制限されるべきではない
だから、今は必要ないpropsも全部受け取れるようにして、元のhtmlタグと同一なものとして扱えるようにしておく
これは別に「例外」ではないmrsekut.icon
「全ての引数をサポートしている」という意味には沿っている
atomsはComponentPropsWithoutRefで型を付ける