殺伐とした「TypeScript全然わからんぼく」に救世主(サバイバルTypeScript)が!
TypeScript素人あるある「よくわからんポイント」だいたい解決しました
- typeで&する時とinterfaceでextendsする時の違いわからん
- ちょっと複雑になるともう読めないアロー関数、ジェネリクスとか
経緯
先日購入した、TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発、
大ボリュームで非常に学びが多い名著なのですが、紙面の都合上、解説が省かれている箇所が多いです。
TypeScriptほぼ初めて(JavaScriptもなんとなくだった)なぼくには
難易度が高い記述も多い本です。
特に6章の実装部分はコード量・質ともに圧倒されます。
現場レベル、ということでしょうか。
学習のために、わざといろいろな記述を見せてるのでしょうか。
正直、心が折れかけてました。
そこに救世主が現れたのです。
「TypeScriptぜんぜんわからない・・・」と
ツイートしてたら、サバイバルTypeScriptの中の人が
いいねしてくれました。
軽く中身を見てみると、
前述したぼくの「よくわからんポイント」が
詳細に解説 してあります。
今日はこれ読み込んでみます。
正直、ぼくの記事の続きを読むより、
↑サバイバルTypeScriptを読んだ方が幸せになれますが、
TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発で
同様に躓いている人もいるかと思うので、
この記事では本書のコードを例に、
躓いた箇所だけざっくりまとめます。
(本書の6.5章で止まっているので、追記する可能性あります)
以下本題
typeとinterfaceって何が違うのよ!
例えば、
// ボタンのバリアント
export type ButtonVariant = 'primary' | 'secondary' | 'danger'
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: ButtonVariant
fontSize?: Responsive<FontSize>
/* 中略 */
}
ふむふむ、特定の型を拡張するには
T & {拡張したいkey: value, key2: value2, ...}
みたいに書けるんだな。
なるほど、TypeScript理解した。
・・・と思うじゃん?
interface RectLoaderProps extends IContentLoaderProps {
width: number
height: number
}
はぁ?
typeの&と、interfaceのextendsの違い、何???
答えは、
interfaceとtypeの違いに書いてあります。
- 同名のkeyを宣言すると、interfaceはオーバーライド(上書き、できないときくはコンパイルエラー)、typeは
never
になる - 同名のtype名を宣言しようとするとエラー、interfaceはエラーにならず(!?)マージされる
といった違いがあります。
同名interfaceにさえ気をつければ、
意図せずneverになることは防げるかな・・・?
ちょっと複雑だともう読めないアロー関数
TypeScriptじゃなくて、JavaScriptの話です。
アロー関数? 知ってる知ってる、
普通の関数だと、
function AddNum(a: number, b: number) {
return a + b
}
みたいなのを
const AddNum = (a: number, b: number) => a + b
みたいに省略して書けるやつでしょ?
TypeScript(というかJavaScript)理解したわ!
・・・と思うじゃん?
function withIconStyle(
Icon: typeof SvgIcon,
): React.ComponentType<IconButtonProps> {
const IconWithStyle = (props: IconButtonProps) => {
const { onClick, className, size = 24, ...rest } = props
/* 中略 */
return (
/* 中略 */
)
}
return IconWithStyle
}
ちょっと複雑になると、もう、読めん・・・
に、だいたい書いてありますが、
function 関数名(引数: 引数の型): 戻り値の型 {
const 変数 = (引数: 引数の型) => {
/* 処理 */
}
return 変数
}
ということをしている。
アロー関数の基本構文は
(引数) => {処理}
なんだけど、ここで引数の型をつけたり、
戻り値の型を指定したり、
処理がreturnしかないなら、()で囲えば省略できたり、
処理が1行ですむなら{}も省略できたりする。(柔軟すぎる・・・)
実際、サンプルコードのAppLogoは、引数いらないので、
ここまで省略できています
const AppLogo = () => (
/* 中略 */
)
あとはthisの指すものが違ったり、
普通の関数ではarguments変数を使うところを
残余引数...
(後述)を使うところだったり、
細かなところが違うことを覚えておこうと思います。
特に理由がない場合はアロー関数を使っておくのが無難らしいです。
{ arg1, arg2, ...rest } = props ←これ何!?
コード読めば推測できる通りの挙動なんだけど、
...rest
は、その左に列挙した要素の他の、残り(残余) の
要素全部取り出してくれる。
見出しの式なら、右辺propsの
arg1,arg2,以外の全部をまるっと持っていてくれる。
既存の型を&やextendsで拡張し、拡張したHTML要素を書く場合、
既存の型で対応可能な要素をそのままうけとりたいため、
Atomsの記述では頻出します。
return <div /* 拡張した要素 */ {...rest /* ←拡張してない要素全部 */ } />
みたいに気軽に書けます。便利すぎか。
わかったようでわからないジェネリクス型
型<型>って何?
型の宣言部分なのに、さらに型を宣言しているよく分からない子。(ぼく、Java経験者のくせに)
答えはジェネリクスに、
例もふまえてわかりやすく書いてあります。
「引数」も「戻り値」も同じ型の場合のバリエーションを作りたい場合、
型をまるで引数のように扱ったコードが作れます。
const Template: ComponentStory<typeof SearchIcon> = (args) => (
<>
<SearchIcon {...args} />
<CloudUploadIcon {...args} />
<PersonOutlineIcon {...args} />
</>
)
↑で、SearchIconにVSC上でカーソルあわせると
SearchIconはComponentType<IconButtonProps>
、
つまり、IconButtonPropsをプロパティにもつコンポーネントだよー、と
教えてくれます。
(args)にVSCでマウスカーソルをあわせると、
typeはIconButtonProps
だよーと教えてくれます。
ここのストーリーではSearchIconのtypeを引数に指定しているが、
CloudUploadIconもPersonOutlineIconも、同じtypeなので
使い回せています。
うむむ、storiesはやってること複雑なので
例には向かなかったです。やっぱ、
ジェネリクスの項
読んでください()
他にも、、、
Omit<T, key>以外にも似たような便利すぎる記述法がある、
値に関数を代入できる理由、
などなど、かゆいところに手が届く説明が多い。
何よりありがたいのが、
全体的に説明が端的、簡潔、分かりやすい!
文法の基礎を学ぼうとすると、時に
- 他の言語との比較とか、
- その言語仕様が生まれた文化とか、
長過ぎるウザ語り短気なエンジニアには丁寧すぎる説明が
登場することが多いが、サバイバルTypeScriptはほぼそんなことない。
今、困ったことが今、解決できるのがありがたいです。
結論
「TypeScriptぜんぜんわからん」状態になったときの救世主!
Discussion