2024/03/16
TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発をやっているが、Nextの話になってきてから分からない箇所が多く困惑しているが、その中での調べたことや調べても分からないことを記す。
本書のSSGによるページの実装で、ビルド時に実行されるgetStaticPropsという機能がある。
この名称の関数をexportすることによってビルド時に実行され、それをもとにサーバーはクライアントへ描画すべき初期レンダリングするページを返す。
性質上非同期関数となる。
静的サイト生成の名の通り、ビルドの際にあらかじめ表示する内容やデータを保存しておくわけだ。
型としてはGetStaticPropsをimportし使用するのだが、どう試行錯誤しても型エラーが発生する。
ジェネリックの型は複雑に見えたが、どうやら2つ以上の引数を型として受け取るらしい。
が、本書ではデータを取得する型、つまりひとつだけしか引き渡していない。
そもそもどうやら本書が出た時点でNextはv12、現在はv14となり変更点もあるのだろう。ファイル構成もpagesやstylesがなくなり、いまはappディレクトリへ一本化されているなど素人目に見ても変化が見て取れた。
まずGetStaticPropsの型は以下の通り。
type GetStaticProps<Props extends { [key: string]: any; } = { [key: string]: any; }, Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = (context: GetStaticPropsContext<Params, Preview>) => Promise<GetStaticPropsResult<Props>> | GetStaticPropsResult<Props>
オブジェクトを引数に受け取り、それをもとにしたPromiseオブジェクトを返す。ふたつめは何やら書いてあるが、変数を見てみると情報を取得してくるURLなどを受け取るのだろうか。
公式にはオプションがあり、それがこのparamsに入ると思われる。
データ取得: getStaticProps | Next.js
ParsedUrlQueryはおそらく組み込みのインターフェイスだろうか。stringかstringの配列らしいので、やはりURLなのだろう。
ParsedUrlQuery | typescript - v3.7.7
今日はいったん寝るため明日以降にするが、どこかデータを安全にとってこれる無難なサイトでも探してみるか。
ただ、zennなどでは型をそのまま割り当ててやるだけで良いという記事も大量に見つかったのだが、変更がきたのだろうか。
ここまでいって戻ってを繰り返すなら一度公式リファレンスを通して読んでみるのもよいのだろう。
ほかにもsatisfies演算子というものがあるらしく、式の後にsatisfies 型とつけることで、型推論のようなことができるらしい。
ただ当然、通常の型割り当てがだめだったのでこちらもだめだった。
nextの機能はさておきいったん続きをやろうかと思ったんだけども、そもそも子コンポーネントへ受け渡しができない。
intrinsicAttributes & がどうこうとか出てくる。
グローバルな型定義ファイルで設定してインポートしてやるとか、とかく記事は出てきても何がどうこうというのはない。
nextの機能なら仕方ないと割り切って進められたが、これはTSのエラーなのでいずれまた出会うであろう。ここで倒せないのが残念である。
ほかの方もおっしゃっていたが、TSはありがたいけどReactと合わさるととたんに難しくなる、と。
厳格なTSの運用は、非常に難しいように感じる。あくまで型チェックだけでも良いのかも…。
あるいはTSに絞った本などで勉強をするか。幸いにも信頼できる方の本を買ってはいるが、ただ、Reactのサポートもしてくれる技術書があればもっと良いのだが。
本書は1年半前ということで多少前くらいの感じだったが、こうしてフロントエンドの技術の進歩は早い。テンプレートフラグを利用してnextプロジェクトを作ったらファイル構成が違うわ、おそらく型が必要になるわ、てんやわんやである。
本書はまだ半分程度しかやっておらず、Reactも知っている部分をなぞっただけ、Nextの冒頭から参考にならなさそうと、このまま続けるか悩むところである。
できない部分は飛ばし、参考になりそうなところをやりたいが……。
少なくともSSGとしてビルドすることには失敗したが、少なくとも通常のやり方であればビルドはできるため、本書でもいろいろと身になる部分があると信じて続けたい。
しかし、本書だけにかかりきりになるわけにもいかないため、他の書籍とも平行せねば。
2024/02/20
本日はuseRefフックとuseImperativeHandleフックの学習。
後者はあまり使われないらしいが、親子をまたいでrefの参照ができ、また状態変更も可能で依存配列によって再描画もタイミングも指定できるものっぽい。
子コンポーネントの関数を親が呼んだりすごい挙動ができるようだが、またいで参照ならpropsやContextオブジェクトなどでもよさそう。
useRefは値の保持やDOMの参照で使われるフック。useStateなどと違い値は変更しても再描画はされない。再描画に関係がないデータの格納に便利。
データはref.currentフィールドに格納される。これは他のものにも言えるが、プリミティブ型ならいざ知らず、オブジェクトが入る場合などもあるため注意が必要だと感じる。
特にサンプルではinput要素を介した疑似的な画像ファイルアップロードのコードとなっており、プリミティブ型と比べ少々複雑な形状をしている。
他にも各種フックにはジェネリック型が指定されているが、本書で既に出ているだけでもHTML要素の型、React要素?の型、File型なども出ている。
そうでなくてもHTML要素の今一度の学習や、増えたCSSプロパティの確認やCSSは何で記述するのかなどから見直す必要がある。
閑話休題。
ともあれuseRefは再描画せずに何かを保持することに長けており、見た目の記法はuseState同様であり、参照させたいコンポーネントへ渡して使うなど用法も変わらなさそうである。
本節のサンプルコードで思ったことはいくつかある。
1つはstrictNullCheckの厳しさ、ひいてはTSの優秀さだ。
そこまで長いコードではないが、シンプルな記法の間違い、タイプミスが頻出し何度もエラーが出ている。以前べた書きでLPを作成した際に感じた「エラー分かるのおせーし場所だけ分かってもわかんねーよ!」という不便さが圧倒的に減っている。ありがたいことだ。
ただ、本書と違いローカル環境ではstrictオプションをtrueにしており、その影響かnullの可能性があるアクセスだよ!というエラーが頻出している…設定変えるか。
各種フックを呼び出す際にジェネリック型を多用しているのだが、<A | null>といった感じで、Aまたはnullの型となっている。strictが全部trueな影響で、各種状態変数やrefなどにアクセスする際、nullかもしれんぞと怒られるという具合。
ひとまずオプショナルチェーン(nullかもしれないオブジェクトへのアクセスの際に?のサフィックスをつけるアクセス記法)でなんとかしている。最初if文中でアクセスしてるのになんでnullかもエラーが出るんだ?型ガード働いてないのか?と思ったけど、strictNullCheck(もといstrictがtur)の影響。
もう1点はHTMLやCSSなどへの理解度の低さだ。
これは上述しているので一時割愛。少々古くなってはいるがuhyo氏のHTML解体新書も買ってあるので、公式リファレンスと一緒に併読しながら読もうと思う。
CSSもここ数年一気にプロパティが増え、サポートされているものも多くなった。subgridなるものも実装されたらしく、またやらねば。
というかそもそもReactwebアプリでCSSを当てる方法をインラインべた書き以外知らない。
2024/01/29
「TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発」の学習記録と覚え書き。
先日同様React Hooksの続き。
ReactのフックでuseMemoから。
useMemo
useCalback同様に値を生成する関数と依存配列を受け取る。
コンポーネント描画時に前の依存配列と今の依存配列の値を比較する。同じだった場合はメモ化した値を返し、異なっていれば渡された関数を実行し結果を新しい値としてメモする。
useCallbackは返すのは関数そのもの、useMemoは関数の返す値、という感じ。改めてリファレンスなどを読んでもう少し理解は深めておきたい。
状態関数など含め、関数自体をメモ化するならuseCallback、コンポーネントの返値など関数が出す結果をメモ化するならuseMemo。
……というか実は昨日useMemoで詰まってできず後日(今日)にしたわけだけども。勘違いでちゃんとできていたようだ。
useEffect
副作用と呼ばれる「コンポーネントの描画とは直接関係ないんだけどやっておきたい処理」をより安全に運用するフック。
副作用含め、Reactの描画、ライフサイクルについてはこちらの記事がよさそうだった。
Reactの関数コンポーネントは各種処理を行いながら、その処理結果を反映させたUIを返す。変化の激しいもの(例としてはタイマーなど)を処理として直接記述すると描画しようとする、変化する、変化したのでまた描画しようとする……と無限ループなどが起こりかねない。これはそれを解決するフック。
関数と依存配列を受け取り、各種描画が全部終わった後に渡された関数を実行する。イメージとしては「重役出勤させる処理を渡すフック」。空の配列でもいいがそうすると初回レンダリング時にのみ処理が行われる。
また渡した関数の返り値だが関数にできる。返す関数をクリーンアップ関数と呼び、、これはuseEffectが行われているコンポーネントが画面から取り除かれる(アンマウントされる)時に実行されるもの。
useLayoutEffect
上記のuseEffectと似たフックだが、処理が行われるタイミングが違う。
useEffectは描画関数(zenn記事でいうところのレンダーか)が実行され、DOMが更新され、画面に描画された後。あとのあとのあと。
こちらは画面に描画される前が違う点。それとこちらは実行する処理は同期的に実行されるため、あまり重いものを渡すと画面出てこなくね?ってなるから注意らしい。
useEffect同様返り値には関数が指定できる。クリーンアップ関数だ。
だけども公式のリファレンス見てみたら「パフォーマンス低くなっかもしんねぇuseEffect使え」とか言われてた。
初回レンダリングとReact18
本書のコラムで興味深かったのが、React18での初回レンダリングの挙動。
<React.StrictMode>以下のコンポーネントでは、初回レンダリングでコンポ―ネントが2回描画されるらしい。道理でコンソールに「なんで2回レンダリングされてる表示されてんだ?」と思ったわけだ。
useEffectもuseLayoutEffectも、その性質上2回関数を実行し、1回クリーンアップ関数を実行することになる点は注意。
ただこれはReact.strictModeを取り除けば不活性化するし、本番環境でも関係はないらしい。
……でも開発環境だとバグ出てるけど「理論上平気!」って本番環境に反映はさせねぇだろうな……。
useContext
非常に苦手意識あるContextオブジェクトのフック。Consumerを介さず直接に値を取り出すフック。
逆を返せばcreateContextでのコンテキスト生成、コンテキストオブジェクトのProviderへの引き渡しはしなければならない。
あとcreateContext呼び出しの際にジェネリック型記述されてるんだけどこれどういうことなんだ。後で調べないと。フック関係の型付け分からん。useStateは分かるけども、これも結局は渡される値の型付けなのか?
useContext(Contextオブジェクト)で渡されている値が取り出せるので、それを変数に収納して直感的に使える。いちいちConsumerで囲まないといけなくなるのは楽だ。
あと素人だから分からないのはそもそもContextってどこで生成してどこで渡すのが正解なんだ?
生成したのをexportしていいのか?まぁ問題はないだろうけど…。そんでどこでimportするのがベターなのか。どこでも使うのはルートから、局所的に使うのは使いそうなとこからでいいのか?
本書ではあと2つの公式フックとカスタムフック実装を経てNext.jsに入っていく。JS、TS、React、Nextとひとしきり終われば次はコンポーネント開発のチャプターへ移る。早くいかねば。
また実際にサンプルコードを写していて思うのは「英単語分かんねぇ!」ってこと。自分は必然的に名付けに非常に制限がかかっている状態。
プログラミング必須英単語600+ | 学ぶ | プログラミング英語検定
これがよさそうなので読みたい。また名付けの語彙が低いのに加え、どう書くのかも全然わかってない。良書と呼ばれているリーダブルコードも買ってあるので読まねば。
2024/01/27
前回記事同様、「TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発」の学習記録。
今回からchapter3のReactの章へ移行。既に別の本である程度はやっているが、TSやReactの型など分からないため最初からやっている。
ReactはJSXという記法でJS、TSファイルの中に直感的にHTMLタグを書けるようなやつ。JSXで作られたReact要素(UI)に振る舞い方や挙動などを抱き合わせるとコンポーネントという単位にもなる。
コンポーネントは関数のように宣言される関数コンポーネント、クラスのように宣言されるクラスコンポーネントがあり、今は関数コンポーネントが主流のようだ(筆者が以前学習した技術書でもそうだった)
コンポーネントとpropsと型
コンポーネントへは任意のオブジェクトをデータとして渡すことができる。それらはpropsの中に格納され、propsに変数名でアクセスできる。いちいちpropsにアクセスするのも面倒なので、分割代入でいったん取り出したほうが楽だろう。
また、コンポーネントの開始タグと終了タグの間にはさまったコンポーネントはchildrenへと格納されるようだ。
コンポーネントの受け取るオブジェクトにも型をつけられるし、おそらくは返す値にも型をつけられるはずだが、まだ現時点では返す値や詳しいReactの型に関してはやっていない。
ただ、関数コンポーネントの性質として、JSX.Element型の値を返すらしい。childrenとして受け取るコンポーネントの型はReact.Reactnode型という広範な型らしいが……詳しくは注釈を読むとしよう。
以前はFC型やVFC型などもあったようだが、今は下火のようだ。
Context
propsでデータを親から子へ渡すのは良いが、いちいち上から下へと渡すのは苦労する。そこで使うのがContext。
Contextコンポーネントを作り、それに値を預け、必要な時に取り出すというもの。
Contextコンポーネントを作ったあとは、Context.Providerと呼びだしてやることで、任意の変数で任意の値を渡せる。
取り出す際は取り出したいコンポーネントの親として、Context.Consumerと囲んでやれば取り出せる。
Contextオブジェクトは以前もそうだったが取り出し方などで躓いたのでまた後で復習する。
React Hooks
React Hooks……フックによって関数コンポーネント内で値を管理、変化させることが可能になった。公式で提供されているものは全15種類あり、それらを組み合わせてオリジナルのフックを作ることも可能。
今日やったのはuseState、useReducer、useCallbackの3種。
useState
名前の通り、状態(ステート)を扱うフック。useStateを関数呼び出しをし、配列に[状態変数, 更新関数]と分割代入をすることで、データとそれを更新・管理する関数が別個で取り出せる。useState呼び出しの引数は状態の初期値となる。
更新関数の戻り値が新しい状態変数へと収まる。シンプルに数値をインクリメントするなどにも使える。
本書をやっていてびっくりしたのは、更新関数にコールバック関数を渡した場合の挙動。そうした場合、仮引数に勝手に状態変数が代入されて処理が行われる。
複数仮引数を記述した場合は分からないが、手間が省ける記法だなぁと感心した。
useReducer
状態を扱うフックなのはuseStateと変わらないが、名前の通りArrayオブジェクトのreduceメソッドのような感じ。オブジェクト、配列などの複数のデータを集めたものの管理が得意。
useStateとほとんど変わらないが、useReducerの引数が1つではなく2つに増えるのが違うところ。1つ目にreducerと呼ばれる更新関数を渡し、2つ目にreducerに渡される初期状態が入るところ。
更に、reducer自身も2つの仮引数を必要とし、一つ目は現在の状態変数、二つ目はactionと呼ばれるデータが入る。状態変数とactionを元に処理を行い、返した値が次の状態となる。
あくまでreducerを介した更新であり、つまりは現在の状態とactionという2つのデータを必要とし、それらをさばいていくフック。
本書では状態変数で数値のカウントをし、actionではどのボタンが押されたかによってインクリメント、デクリメントなどカウントを操作するというswitch文でのテストコートがあった。
useCallbackとmemo
useCallbackとuseMemoはどちらも値や関数を保持(メモ化)し、不必要なレンダリングを避けるためのフック。だが、今日ははmemoとuseCallbackに留まっているので後日useMemoを追記する。
Reactはコンポーネントが再描画されるタイミングはいくつかあるが、中でも注意しなければならないのは「親コンポーネントが再描画された時」。
ある親コンポーネントが再描画されると、それだけで下にある子以下のコンポーネントは再描画される。そうやって無駄なレンダリングを避けるために使われる。
memoは、関数の頭につけてやると簡単にその関数をメモ化できる。
関数コンポーネントのサンプルコードではジェネリック型が続いているのが見受けられたが、付けられるだけなのか必須なのかは分からない。後日調べよう。
これによって親が再描画されても「propsが変化しない限りは同じものを返し続ける」コンポーネントが誕生する。
ただし、渡されるpropsが親の再描画によって新規生成されると意味がなさそうだ。親から子へ何かのデータが渡されていて、それが親コンポーネントの中で生成されているものだとしよう。
親が再描画される=また全部処理をやり直しなので、結局データも再生成される。ということはメモ化が意味をなさない。「新しいデータがきたから自分も再描画しないと!」と、せっかくメモ化したはずのコンポーネントが再描画される。
またメモ化フックにuseCallbackがあるが、上記のmemoよりもより厳しい判定のメモ化ができる。こちらは名前の通りコールバック関数を受け取り、それを保持する。
useCalbackは第一引数に関数を、第二引数に依存配列と呼ばれる配列を受け取る。
上記のmemoと違い上から渡されるpropsではなく、依存配列に変化がない限りは同じ関数を返す。親から渡されるpropsだろうと、Contextの値だろうと、依存配列に関係がなければ変化は起きない(新しく関数を生成しない)。
空の配列を渡すこともでき、これはReactアプリの初回レンダリング時以降同じとして扱われ、二度と関数を生成はしない。もちろんF5キーなどで全体を更新した場合には違うけども。
今日はだいぶ手が遅かったのと、メモ化フック両方やるつもりだったが果たせなかった。明日リベンジ。
2024/01/26
先日購入した「TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発」で、TSやフレームワークであるNext.jsを触れつつ実際に開発を経験してみる。その学習記録。
またうひょ氏こと鈴木僚太氏著の「プロを目指す人のためのTypeScript入門」も併読している。TSの挙動や仕様を知るにはうひょ氏著作を、実際に作ってみる教科書としてアプリケーション開発のほうを拝読している。
記事内容は学習その記録。
「TypeScriptとReact/Next.jsでつくる実践webアプリケーション開発」chapter2.6では各種開発時の設定についての節がある。
中でもtsconfig.jsonファイルのコンパイルに必要なオプションに関して調べたものを以下書き記す。
公式リファレンスとこちらのQiita記事を参考にさせてもらったため、随所引用させて頂く。
また調べた内容は本書に出てきたオプションのみ。
以下compileOptions配下の各フィールド。
targetフィールド
ここで指定するのはどのECMAscript、つまりJSのバージョンなのかという指定。コンパイルの際のバージョンとなるため、Qiita記事ではBabel的役割とも記されている。
公式曰く、最新のブラウザはES6までのすべての機能をサポートしているため、ES6にすることを推奨しているようだ。
古いバージョンにすることもできるだろうが、新しくプロダクト?プロジェクト?をスタートする際は新しいバージョンである方が良いと思った。
またESNextにするとES6であると同時に、次にくるであろう仕様などを踏まえたバージョンにすることができる。
本書ではES5と記述がなっているが、6で問題ないだろう。
libフィールド
コンパイルの際の組み込みライブラリを指定するフィールド。targetで指定したものに組み込まれているものは暗黙的にここにも入るらしい。配列として複数列挙が可能。
そのため、JSのバージョンだけではどうにもならないものを追加でコンパイルするためぶち込む。公式リファレンスによればブラウザ環境のdocumentなどがあるようだ。
実際本書ではDOM、DOM.Iterable、ESNextがここには入っている。ブラウザ環境を再現するためにDOMはいるだろうし、DOM.Iterableもおそらくいるのだろう。
ただし、明示的に指定すると、暗黙的に指定されていたものはオートで指定されなくなるので、必要なものは全部列挙する必要があるらしい。
ブラウザ提供のライブラリはここで読み込むため、必須のフィールドと言える、と思う。
allowJsフィールド
プロジェクト内で、.jsと.jsxファイルをインポート可能にするするかどうかのフィールド。Qiita記事によれば、これによってコンパイル対象となる。
公式リファレンスを読むとTSファイルはデフォルトでインポート可能で、JSも同様にインポートするか決められる。
筆者はそもそもTSの開発経験どころか、開発そのものの経験がないため分からないが、自分で作ったファイルのインポート/エクスポートのみならず、ライブラリを使う際にもここはtrueでいいのではないだろうか。
skipLibCheckフィールド
Qiita記事には書かれていないため主要ではないオプションなのだろうか?
公式リファレンスによれば「宣言ファイルの型チェックをスキップする」らしい。
型システム精度は犠牲になるがコンパイルの時間を節約できるらしい。
すべてのファイルを完全な型チェックをするのではなく、d.tsファイルによる型とソースコード内の型チェックで済ませるもののようだ。
何やら複雑そうなことがかいてある。TSのバージョン更新によって起こる諸問題をいったん無視するようなオプション……?
strictフィールド
これをtrue(オン)にすると関係するstrictオプションが全部true(オン)になるようだ。関係するオプション(ファミリオプション?)は8つくらいある。名前の通りめっちゃ厳格なんだろうな。
オフにしておけば、個別のstrictオプションを別個設定できるようだ。
本書ではfalse(オフ)になっている。どっちにすべきかは分からんけどとりあえずオンでもよさそう。
strictNullCheckフィールド
これをオンにすると、nullまたはundefinedなアクセスに対してエラーを吐くようになる。
例えばオブジェクトにアクセスした時などに「trueになる何かが保証されていないなら、エラーを出す」という感じ。
おそらくは型によって中身を保証しろ、というモードだろうか。
一応、これをオンにしていてもアクセスの際に!(non-null assertion)演算子をつければ回避できるらしいが、TSのコンセプトとそぐわないため基本的に使わないテクニックだろう。
オンにしていてよさそうだ。
forceConsistentCasingInFileNamesフィールド
これはインポートする際の名前と、インポート元のファイル名を参照して「お前ちゃんと大文字小文字もちゃんとあってんだろうな」とチェックするオプション。
例えば、「HogeFoo」というファイルを「hogeFoo」とインポートすると怒られる。
名前の通り「ファイル名において一貫性あるケースを強制する」オプション。
大文字小文字での間違いなんてしょうもないし、それが潰せるなら結構いいのでは。
オンで問題はないと思う。
noEmitフィールド
TSCを走らせた際に、結果であるJSファイルを出力するかしないかのオプション。
発する(emit)にnoがついてるので名前の通りと言える。
これをオンにすることでBabelなどの別のツールがコンパイルを担当することになるだろう。
あくまでTSの型チェックだけを目的にする場合にも有効。
これいまのところオンとオフどっちにすべきか分からん。本書ではtrueになってるので出力はされていないが。
esModuleInteropフィールド
翻訳によればESモジュールの相互運用性という部分。
古いインポート方法を今の記法に直してくれるオプション、のはず。
CommonJS/AMD/UMDのモジュールをES6モジュールと同様に扱うらしい。
筆者はなじみがないが、古いJSではモジュール機能がなく、require関数を用いてインポートしていた。新しい記法を用いても古い古い記法でも通じるように直してくれる…んだと思う。
Qiita記事には載っていない。
オンで問題ない、よな?
moduleフィールド
モジュールのシステムはどの記法とかバージョンにする?っていう項目のようだ。
CommonJSやES2015、ESNextなどtargetフィールドみたいな感じで指定してやる必要がある。
モジュールって何回も出てくる上にモジュールだけのフィールドもあってびっくりした。
モジュールっていっぱい出てくる上にまたモジュールかよとびっくりした。
とりあえずES6とかESNextでいいんじゃないかなぁ……。
というか調べててES5が2009年、ES6で2015年てびっくりした。
moduleResolutionフィールド
TSCがモジュールを解決(読み込む?)際の方法を指定するフィールド。
またモジュールかよ!と思ったけど、これはTSコンパイラ自身への設定のようだ。
オンオフではなく既定の文字列を指定してやる。
何やらモジュールインポートがパスを指定してやる相対的、シンプルに名前だけ書く非相対的インポートの解決方法に関するものらしい。なんか難しい。
デフォルトがnodeであり、他にもbundlerやclassicがあるようだがnodeでよさそうだ……。
resolveJsonModuleフィールド
.jsonを含むモジュールをインポートできるようになるオプション。デフォルトだとできないんかーい。
オンにすることでJSONが使える。便利。
しかし公式で静的なJSON形状に基づいたタイプの生成が含まれる、と書いてある。つまりJSONごとに形状に応じた型が生成されるのか…?deepl翻訳だから分からん。
Qiitaにも乗っていなかった。
オンで問題はないだろう。逆にオフだとJSONなんか読み込まねぇぞとか起こりそう。
isolatedModulesフィールド
Deeplによると分離されたモジュールらしい。これをオンにすると単一ファイルのトランスコンパイルプロセスで正しく解釈できない特定のコードを記述した場合に警告するようになる、らしい。分かんねぇ。
Qiita曰く、コンパイル対象のファイル感の関係性を無視し、すべてのファイルを単一のモジュールとしてコンパイルする、らしい。分からん。
trueにした場合、コンパイル対象すべてがexportを含む対象となるらしい。
また、インポートした型をそのままexportする?型re-exportもエラーが出る。
本書ではtrueになっている。
インポートエクスポートを繰り返すことで、fluxになるからか?
jsxフィールド
コンパイルの結果出てくるJSファイルにJSXがどう反映されるかのフィールド。tsxファイルをjsxやjsにコンパイルする際の出力形式。
ただQiita記事がdeclareキーワードなども使っており、ずぶの素人である自分には些か難しく感じる。
preserveはjsxをそのまま地保持するため、tscコンパイラ以外にコンパイルさせる場合に有効なようだ。
ただ、型としてraectがインストールされているならjsxの型は必要ない?(--savedevフラグ付きで@types/reactをインストール)
preserveかreactでいいのだろうか。本書ではpreserveになってる。
TSにおけるJSX記事はここが分かりやすいそうだ。ってうひょさんじゃないか。
baseurlフィールド
非相対的(non-relative)なインポートにおいて、相対的なカレントディレクトリがどこかを指定する。
そのため、パスを記述してインポートする相対的(relative)インポートには影響が出ないようだ。
これはnode_modulesの検索よりも優先されるようだ。
とりあえず本書ではsrcディレクトリが指定されている。自分はtsconfig.jsonファイルがルートディレクトリにあるため、とりあえずパスで./srcとしているが変わらないのかな。変わるのかな。
以下、compilerOptionsフィールドを抜け、includeとexcludeフィールドへ。
includeフィールド
ここには配列で記述でき、コンパイル対象となるファイルやその条件を記述する。
正規表現じみた*や?などのワイルドカード?というのが仕様できるらしい。そのため、コンパイル対象を全部相対パスなどで記述する必要はない。
excludeフィールド
逆にコンパイル時に除外されるファイルを指定するフィールド。
だが正確には「エントリーファイルからは除外するけど、それ以外には寛容しない」。
babelなどのツールは複数のファイルをまとめて単一のファイルを出力するが、その起点となるファイル(エントリーファイル)からは読み込まないだけで、エントリーがインポートしたファイルそのものが除外ファイルを更にインポートしている場合、結果読み込まれる。
読み込むファイルの優先度としては、encludeよりも高いようだが、filesという設定がありそこにあれば除外されていてもコンパイル対象になるらしい。
難しい…。
他にも本書ではprettier、ESlintなどに触れているが割愛。
本節をもってchapter2は終わり、chapter3からはReactを用いた開発が始まる。
積み上げ消化:プログラミングTypeScript第1~3章
学習した技術書「オライリー社 プログラミングTypeScript」の学習した内容。
学習記録を兼ねた備忘録。
第1章 イントロダクション
型安全性とエラー発見タイミング
第一章はイントロダクションに過ぎず、内容はほとんどない。
読んでみたところ、TypeScriptの恩恵はひとまず、型安全性とエラー発見タイミングに尽きる。
型安全性とは、「型を使ってプログラムが不正なことをしないように防ぐこと」、と書いてある。
4章まで学習した結果の自分の解釈はこうだ。
JSにはそもそも暗黙的な型変換があり、属するクラスや型によってプロトタイプも継承する。自由度こそ高いが、ゆえにエラーも起きやすい。
不正な処理を興さないように、変数に明示的に型という通行手形を与え、処理の際にこの通行手形を確認するというもの、だと思っている。
例えば本文では文字列と数値の加算演算子の処理を例に挙げている。普通せんやろと。なるほど確かに。
わざわざ型の違う値同士を使用する処理なんてないだろと。そういう処理をルールも設けずにやっていると、アプリが肥大化するにつれてレガシーコードになっていくのを防げる…のがこの基本の恩恵なんだと思う。
エラー発見タイミングも大事だ。エディタでコードを書いている最中にエラーが発見できるのは大きい。
さらにはエラーには型の情報も出るため、なぜそれが不正(と判別された)処理なのかもわかりやすい。
エディタとブラウザコンソール画面を行き来しなくて済む。
第2章 全体像
型チェッカーと型システム
実際のコードはTypeScriptコンパイラーによっていったん空白などが取り除かれるが、この際に型チェッカーによるチェックが入る。つまりは実際に実行前に型チェックがされるというわけ。
型チェックは実際の挙動には影響しないため安全なのもポイント。
本文ではより詳細にコンパイラーによってソースコードがASTに解析され、それがバイトコードにコンパイルされ、ランタイムによって評価されることで初めて結果が得られる…という工程があるらしい。
TSコンパイラーが更に一枚かむ…というわけだけどそこはいったんおいておこう。
大事なのは、実行前にチェックができること、さらには型はチェックの際にしか作用しないため安全ということ
型のシステムには2種あり、アノテーションによって明示的に宣言するものと、システムによって自動的に推論させるもの。
後者は必然的に通常のJSと構文が変わらないため割愛。
アノテーションは「値 : 型」という構文で値=変数に型を与えるというもの。
例えばnameという変数に文字列の型を着けたい場合、let name : string = ...となる。プロパティの記述みたいな。
アッパーキャメルケース記述だとJSのクラスになるので注意。
すべての値に型の保証がなくてもコンパイルは可能。だが明示的であれ推論させるのであれ型保障は100%を目指すべき、とのこと。
アノテーションによる明示的な宣言はどこで使うべきか。
基本的には推論で良いけど、大事なところで使う事。同様に型変換も明示的にやったほうが良いとも書いてある。
第3章 型について
型について
型とは、値とそれを使ってできる事柄の集まり。
例えば数値……5があったとして、それを加算したり、数値クラスのメソッドの使用ができる。これが型。
値そのものだけでなく、それが何をできるのか(何kができないのか=してはいけないのか)も重要。
値を使って不正な処理をしないようにするという型安全性を思い出すと納得だ。
数値を受け取り二乗した値を返す関数があったとして、パラメータに数値型がアノテートされていると文字列などを渡して呼び出した際にエラーが出てくる、という感じ。
型にはJSのオブジェクトやクラスのようなもので、それをさらに絞り込んだリテラル(実際の値)がある。型にはリテラルも指定できる。
numberという型には色々なリテラルがあり、1や50などの整数から浮動小数点なども含まれており、これがリテラル、という感じ。
またconstでの変数宣言だった場合、型は代入されてるリテラルになる(プリミティブ値は少なくともそう)。letやvarでの型指定にはリテラル型も使用できるが、代入ができなくなる。
型の種類
any型
推論に任せてもシステムが分からなかった時のデフォルトの型。最後の手段であり、基本的に乱用は避けるべき。こればかり使ってしまえば、通常のJSとほぼ変わらないため、せっかくの型安全や型チェッカーが死ぬ。
unknown型
少数ケースだろうけど、型が分からない値の場合anyではなくこれの使用を推奨しているっぽい。
任意の値を表すものの、チェックすることで型の絞り込みが進むという特性がある。
比較等でチェックができる(比較自体はできるし、おそらくこの時に型の推論が進む?)ものの、特定の型であることを想定した処理はエラーになる。
比較、否定、typeofやinstanceof演算子での絞り込みが可能。
boolean型
真偽値型、ブーリアン。JSと同じくリテラルは2つしかなく、比較等はできるもののあまり多くのことはできない。
真偽値を変数に代入すると型はbooleanとなる。型にtrueを指定した場合、falseは代入ができなくなる(逆もまた然り)。
number型
整数、浮動小数点、正数、負数、NaNなどすべての数値の集まり。
四則演算や剰余、比較などもできる。
letでnumberだけでなくリテラル型指定ができ、constでり手たる肩を推測させることもできる。
bigint型
nunberとは独立した型で、丸めのエラーに遭遇せず大きな整数を扱うことができる。
JSにとってもTSにとっても比較的新しい概念であり、使用する際は注意。すべてのプラットフォームで実装されていないかもしれないので。
まぁ自分の環境だと使わなさそう。
string型
名の通り文字列の型。本書には積極的に型推論をさせるべきとある。
プリミティブ値は基本推論させた方がよさそう。constキーワードでの宣言は代入負荷なだけでなく型がリテラル型にもなるし、イミュータブルな値は積極的にconst宣言すべきなのかもしれない。
symbol型
ES2015で導入された機能。できることは大きくない、らしい。symbol型は固有の(一意な)値を持つ。例えば文字列aという値のsymbol型を2度生成しても同一にはならない。
型アノテートの際にuniqueキーワードを併用することで他でいうリテラル型のような振る舞いをする。
オブジェクト型
オブジェクト型はオブジェクトの中身=形状を指定する。アノテートの段階で中身を記述する感じ。受け取るオブジェクトが明確な場合はこれか。オブジェクトリテラル表記というらしい。
空のオブジェクトなどでアノテートしても良いが、それだけでは挙動はany型とほとんど変わらないことは注意。
オブジェクトは特定のプロパティを持つことを重視する構造的型付けという考え方があるようだ。おそらくオブジェクトが特定の型のみを持つことを是とする考え方。
アノテートの際の形状は通常の記法では必須のプロパティ、識別子の後に?をつけると省略可能を意味する。
[key : 型]という表記で、特定の型で表されるキーを複数持つ可能性を示唆できる。これをインデックスシグネチャと言うらしい。エディタではこの意味合い以外で出てきたことがあるが、まぁ形状の表記自体を指すこともあるのだろう。はたまた自分の記憶違い。
フィールド名の前にreadonlyをつけると読み取り専用のフィールドにすることもできる。const宣言のようなものだろう。
基本的にはオブジェクトリテラル表記(いわゆる形状の表記)かobuject型のアノテートを推奨。何を受け取るか限定できる場合や構造的型付けオブジェクトなら前者、オブジェクトが必要なだけの場合は後者。
型エイリアス
変数宣言のようにで、typeキーワードで型の別名(エイリアス)を宣言することができる。
別名の名の通り、本来指している型に置き換えることもできる。型に明確な意図を持たせたい場合や、何度も型を繰り返すのを省く(Don't Repeat Yourselfの略)際に有効。
合併型と交差型
型アノテートは単一の型以外も指定できる。いずれかの型を持つ場合は合併(union)型、すべてを兼ねる型の場合は交差(intersection)型という。
or演算子「|」とand演算子「&」でアノテートの際に複数型を列挙できる。
合併型はいずれか型を持つだけでなく、他方の型も持てる。短絡評価のようにどれかの型がtrueになったら後者の型を持てない、なんてこともなさそうだ。
交差型は指定した型すべてを持つ。
配列
配列はやや特殊なオブジェクトであり、型を推論させる場合だと、配列の値が持つ型は初期化された際の型に依存するようだ。
例えばnumber型を値を入れた配列は、pushで文字列を持たせようとするとエラーとなる。
空の配列のアノテートだと中身はany型となるが、操作を減ることで型推論が進む。
タプル
タプルは配列のサブタイプであり、配列を片付けする際の特別な記法だ。インデックスは特定の型を持つ。ただ記法は配列と同じ。
他と違い、宣言したら明示的アノテートが必要となる。
性質上、固定長の配列の片付けに近いが、可変長要素もサポートしている。可変長要素となる型の前にドット3つ。デストラクチャリングのようなものだろう。
読み取り専用配列とタプル
通常の配列はミュータブルだが型アノテートの型の前にreadonlyやReadonlyをつけることでイミュータブル配列やタプルが実装できる。
性質上明示的アノテートが必要。
readonlyとReadonlyはやや記法が違う。
読み取り専用であるが実態はJSの配列と変わらない。破壊的メソッドの使用ができないため、何でもかんでも読み取り専用にすると大変になりそうだ。
null、undefined、void、never
JSには欠如を表す値として値そのものがないことを示すnullと、未定義を示すundefinedがある。
Typescriptにはこれに加え、関数が何も返さない場合の戻り値voidと、例外のスローをする関数や無限ループの関数を示すneverがある。
すべてを包含するunknown型がすべての上位型(スーパータイプ)だとすると、noverは全ての型が持つ下位の型(ボトムタイプ)。どこでも安全に使える。
…使うかはさておき、どこでも明確に型の意味やできることが指定されてるということだろう。
列挙型
列挙型はある型について取りうる値を列挙する方法…らしい。
順序付けされないデータ構造でキーが値となる。配列の逆バージョン、と考えてよいだろうか?
2種あり、入力された文字列に数字を対応させる数値列挙と、文字列と文字列を対応させマッピングする文字列列挙だ。
enumキーワードで宣言され丸括弧を省略した関数のような形を持つ。
例えばa、b、cという文字列を持つ列挙型は、aにアクセスすると0、bで1、cで2と、配列のキーと値を逆にしたような形になる。
ちょうどアクセス方法もドット記法と各括弧記法だ。
明示的に値を示すこともでき、キー名 = 値となる。例はこれを省略したデフォルトの数値列挙。
一度に宣言するのではなく、分割して宣言することもできるらしい。宣言のたびにArray.pushの如くマージしていくとか。
letやvarキーワードは定義していないキーへのアクセスを許可しちゃうが、const宣言だとこれを許可しない!安全だね!
只今4章の関数の項目をやっているので一通りやったら備忘録としてここにアウトプットする。
積み上げ消化:Reactハンズオン 環境構築(npm、eslint)
Reactハンズオン第2版の本編自体は終わったものの、付録部分の環境構築(主にコーディングで使用するツール)の部分がまだだったので今日はそこをやった。
各種ツールはなんとなく使っていたため非常に為になる。公式ドキュメントなども読むとして…いったんは本書の書いてある内容をまとめる。
また本書は如何せん最新の本とは言えない。eslintの設定ファイルを自動生成する際などに古さを感じる部分があった。自動生成の質問の際にフレームワークの選択肢にNextが出てこなかったのだ。
公式ドキュメントも明日にでも読んでみよう。
npm
npmはNode.jsのパッケージマネージャー……というと非常に簡潔なのだろう。
Node.jsという凄い働き者に取り次いでくれる担当者がnpm、だと思っている。文字通りマネージャー的な。
そもそもNode.jsはサーバー上でJSを実行するためのフレームワーク?らしいのだが、まぁサーバー上(便宜上このPC上)で色々頑張ってくれるもの。
パッケージ配布のためのデータベース(npmレジストリというらしい)とパッケージ管理のためのCLI(Command Line Interface)の両方を指す。
主にコマンドラインでnpm、npmと繰り返すためどちらかと言えば後者の側面がなじみ深い。npm コマンドで「npm君、これやっといて」みたいな。
これを更に改良?したらしいnpnmだかとかもあるらしいし、Yarnなど他のCLIもあるらしいからややこしい。
ひとまずNodeに取り次いでくれる人、という認識。
その性質上Nodeがグローバルにインストールされていればnpmもどこからでも使用できる。
npm initコマンドをプロジェクトフォルダで実行すると初期化ができ、-yのオプションで初期化の質問を省略できる。
npmでプロジェクトを初期化するとpackage.jsonというプロジェクトの設定に関するファイルが生成される。
package.jsonと依存パッケージ
このpackage.jsonはプロジェクトの情報が格納されているが、中でも重要なのは依存パッケージのリスト。
dependenciesとdevDependenciesがそれ。
依存パッケージとは「これがないとこのプロジェクト成り立たないよ!」という必要なのでインストールしてあるライブラリ、だと思う。
npm install(省略はnpm i)コマンドでライブラリをインストールするとこの依存パッケージの部分にパッケージの名前とバージョンが追加される。
開発の為だけに必要なツール(テストやコード整形に必要なものなど)は--save-devや-Dオプションをつけてインストールしよう。
アプリが実際に動くのに必要なパッケージはオプションなし、アプリを作ることだけに必要なパッケージはオプション付きでインストールしよう。
オプションなしだとdependenciesへ、オプションありだとdevDependenciesのフィールドへ追加される。
既存プロジェクトの場合は、githubからcloneしたプロジェクトのルートディレクトリでnpm iを走らせるだけでいい。
存在しているpackage.jsonの依存パッケージリストを参照し、その対応パッケージがインストールされる。
ただ、対応パッケージとはいえ最新版が勝手にインストールされるので、固定のバージョンが使いたい場合は後述するpackage-lock.jsonを参照したインストールを試すこと。
またpeeDependenciesという、直接依存関係にはないが、あるパッケージがインストールされているときにインストールされる必要のある依存関係リストもある。
依存パッケージのアップデートがあるかは、npm outdatedで確認できるらしいが、本PCでは動かなかったっぽいので何かあるのか、それともないのか。
package-lock.json
npm installを実行すると、package.jsonとは別に生成されるpackage-lock.jsonは依存関係に特化したファイル。
node_modules配下の依存パッケージをすべて列挙しており、ロックファイルとも呼ばれる。
依存パッケージだけでなく、依存パッケージが依存しているパッケージなども覚えており、名の通り依存パッケージのバージョンを固定する目的で使用される。
npm iでは対応パッケージの最新版が勝手にインストールされるが、npm ciではこのpackage-lock.jsonで指定されたバージョンがインストールされる。
通常、npm iを使用する都合上、こちらも更新されてしまう。
性質上、手動編集せずバージョン管理システムで管理するのが良いようだ。
コマンドの定義と実行
npm {コマンド}で何かしらのサブコマンドを実行するのは言うまでもないが、頻出するコマンドは自身で定義し実行することができる。
コマンド定義をする場所はpackage.jsonのscriptsフィールド。
ここに"CLI上で呼び出す際のコマンド : "実際に呼び出されるコマンド"と定義する。
ここで定義したものを呼び出す際はnpm run {コマンド名}で呼び出せる。
プロパティの値にはシェルで実行可能なコマンド、もしくはnod_modules配下にインストールされているCLIを文字列として指定する必要がある。
つまり、実行できるかインストールしてあるCLIのコマンドを”文字列”として保存しておく。
一部コマンドはrunを省略できる、らしいが……何が定義済みかそうではないかは分からない。runひとまずつけときゃ問題ないかな?
パラメータ
先ほど定義したコマンドを実行する際にパラメータを渡すこともできる。
scriptsフィールドでコマンドを定義する際に事前にパラメータを渡すこともできる。
事前に定義せずコマンドプロンプト上で呼び出す際にパラメータを渡すにはパラメータの前にさらに--をつける。つまり、
npm run {コマンド} -- --{パラメータ}で呼び出したコマンドにパラメータを渡せる。
npm run {コマンド} --{パラメータ}ではnpm本体にパラメータが渡ってしまい、予期せぬ動きとなる。
npx
npmコマンドに次いで頻出していたnpxコマンド。
scriptsで定義せずにCLIを直接呼び出せるコマンド。ローカルにインストールされていないCLIもリモートで呼び出して実行してくれる。
npmはNode.jsのパッケージマネージャーなので、npmはNode本体を一度呼ぶ…と解釈している。npm run {コマンド名}はnpmがNodeを呼んでNodeがまたCLIを呼ぶ、みたいな感じだろうか。これはNodeではなくインストール済みのCLIを直接呼ぶ。
単純に事前定義されていないNode以外のCLIを呼び出す際のコマンドと覚えてもよさそうだ。
npm runコマンドは性質上、一度インストールしなければいけない。インストールするということは使う頻度に関係なく依存パッケージが肥大化してしまう。
そこまで使わないコマンド、依存パッケージに列挙するほどでもないコマンドを実行したい場合はnpxコマンドで呼び出そう。
npmバージョンが7以降ならnpm execコマンドがnpxのアイリアスとしてあるらしい。
参考リンク
npmおよびnpxの公式ドキュメント
後悔パッケージ検索用のwebサイト
Eslint
EslintはJavaScriptの静的解析ツールで、構文の誤り(シンタックスエラー)を検出するために使われる。
設定は手動で記述も可能だが、自動生成も可能。npx eslint --initで自動生成が走り、いくつかの質問に答えるだけでファイルが自動生成される。
.eslintrc.jsファイルに各種設定が格納される。
envフィールド
これ以降は.eslintrc.jsのフィールドの話。
envフィールドはJavaScriptの実行環境を指し、ブラウザやNode等複数の値を設定できる。
デフォルトではブラウザでの実行がtrueになっており、またes2021もtrueになっているためブラウザやES2021で提供されているAPI各種が使用できる。
設定可能なすべての値のリストは公式ドキュメント参照。
https://eslint.org/docs/user-guide/configuring/langage-option#specifying-environments
extends
extendsフィールドは継承する他の設定ファイルの記述が行える。
ESLintは複数の設定ファイルを持てる上、それらの設定をマージして使用できる。
実際に継承するファイルは、ローカルファイルへのパスか、共有設定を指定する。
またextendsに設定したい共有設定はローカルへインストールされている必要がある。
デフォルトではeslintの推奨ファイルが指定してあり、ここにReactの推奨ファイルを追加するとしよう。
その場合"plugin:react/recomend"と記述することで、reactの推奨設定ファイルが共有される。
npmで公開されている設定ファイルはeslint-config-が名前の頭についており(プレフィックス)、様々なファイルが公開されている。
このプレフィックスは省略可能なため、先の例のreactの前にはプレフィックスがない訳だ。
また設定ファイルの中で共有したい設定をスラッシュの後に記述する必要がある。詳しいことは分からないが、recommended(推奨)でよさそう?
詳しくはESLintの開発者ガイドを参照とのことらしい。
https://eslint.org/docs/developer-guide/working-with-plugins#configs-in-plugins
parseOptions
ESLintはデフォルトではES5までしかパース(文字列変換、だよな?)できない。と書いてあるけど現状ecmVersion : latestとなっているため最新版まで勝手にパースしてくれそう。
パースの設定を記述するフィールドなことには変わらない。
より新しい構文やJSXなどをパースするためにはこのフィールドに適切な設定を記述する必要がある。
一応注意だが、ReactのパースとJSXのパースは別設定なので気をつけよう。
またECMAScriptの構文パースとAPIのパースも別。これらは別途設定しよう。
読み込む構文やAPIはどこまで新しいのを対応しますか?ということなのだろう。
plugins
ESLintはデフォルトではJSをパース(とチェック?)するだけ。つまるところここ(extendsフィールド)に何かを記述しないとReactなどをチェックしてくれない。
よってJS以外(Reactなど)のチェックをするかどうかの場所。
手動で記述もできるが、ルールが定義されたプラグインを使用できる。
npmで公開されたプラグインにはeslint-plugin-のプレフィックスがある。extensでの共有設定と同じくこれを省略して記述できる。
デフォルトではReactを使うと答えたためか、reactが記述されている。プレフィックスが省略されているが、ちゃんと記述するとeslint-plugin-reactとなる。
またローカルにインストールされている必要があるため注意。
rules
ここにはルール名とその設定値を記述する。
設定値はoff(または0)で無効化、warn(もしくは1)で抵触した警告は出すが処理は続行、error(もしくは2)で抵触した場合エラーを出力し処理を中断、となる。
デフォルト出力の段階ではこのrulesは空だが、extendsで外部ファイルを継承しているため、それをそのまま使用している。
extendsで継承したファイルの設定以外を追記するところ、といったところなのかもしれない。
継承した設定の上書きなどもできる。
実行と.eslintignore
チェックはよく使うのでpackage.jsonのscriptsフィールドに記述しておいても良いだろう。
サンプルでは"lint" : "eslint ."と事前に定義している。
npm run lintを打てば、npx eslint .と同じ処理が実行される、というわけだ。
gitなどもそうだが、パラメータに「.」と渡すことでカレントディレクトリ配下の各ファイル(ESLintの場合はJSファイルだが)が対象となり処理が実行される。
git同様、特定ファイルを処理対象から除外できる。.eslintignoreファイルでそれらの設定は管理。
また構文チェックと共にユニットテストも行う場合もあるらしい。複数処理はscriptsフィールドに&&で複数のコマンドを実行するように定義するっぽい。
参考リンク
ユーザーガイド
https://eslint.org/docs/user-guide/
開発者ガイド
https://eslint.org/docs/developoer-guide/
本日の備忘録はこんな感じ。
本書の付録はあとPrettierとwebpack、babelとあるが、これらを使えてもあくまでフロントエンドのコーディングのツールをある程度理解したに過ぎない。
CSSのメタ言語などもあるし、フレームワークのNextだって本書では触りしかやらないし、実際になんのライブラリが有用でどう使えてそれらは商用利用できるかどうかと知るべきことはたくさんある。
良くも悪くもネットで人がつながる時代、技術は日進月歩で進む。HTMLやCSS、JSだって自分は多少理解しているが、こうしているうちにも段々と新機能が開発され、標準化されていく。
付随するものと分野が多い、多いが、こうやってちょっとずつ知見を増やしていきたい。
如何せんインプットも早いほうではなかったのにこうやってアウトプットまでやると時間が本当になくなる。
効率化しないといけない。