はじめに
筆者は2022年8月に株式会社ニジボックスに入社し、翌月の9月に現在のプロジェクトにアサインされました。
入社時はフロントエンドの実務経験はほぼゼロで、HTML/CSSやJSの基礎的な文法・ルールをスクールや独学で勉強していた程度の新人エンジニアでした。
そんな私がいきなりTypeScriptでNext.jsをガリガリ書くようなプロジェクトにアサインされ、困ったことや技術的につまずいたところを備忘録としてまとめていきたいと思います。
(読者の方が私と同じ轍を踏まなくて済むように、先輩方からいただいた解決策や私なりの解説を載せておきます)
対象読者
- フロントエンド初心者、実務経験があまりない人
- これからNext.jsやTypeScriptなどを扱う業務もしくは学習を始める人
前提
フロントエンドの実務経験が少ない筆者が、あくまで同じような技術レベルの方に向けて書いた記事です。
この記事を書いている人
前職はSES企業にて2年弱エンジニアをしていましたが、未だに自分がどういうエンジニアだったのかよくわかっていません。社内サーバにPHP製のCMSを構築したり、PowerShellでシェルスクリプト書いたり、オフショア開発で通訳・翻訳をしたり、他にも色んな業務をしていました。フロントエンドの技術はノータッチでしたが、バックエンドは半年間ほどNode.jsによるAPI開発に携わったりしたこともあります。
なので、色々とやってはみたけどフロントエンドの実務はほとんどしたことない人1が書いてる記事です。
プロジェクトで使っている技術
アサインされた開発案件のレポジトリで使用されている主な技術は、以下の通りです。
- TypeScript
- Next.js
- React Hook Form
- Zod
- Jest
- React Testing Library
- MSW (Mock Service Worker)
- Storybook
困ったこと、技術的に詰まったところ
つまずきポイントとしては、以下のようなものがありました。
- ジェネリクスって何ぞや? HTMLのタグ以外で
<>
なんて見たことないぞ...? - getServerSidePropsってなんとなく知ってるけど、具体的にどうやって使えばいいんだ?
- Zod? MSW? なんかライブラリの使い方が全然ワカラン...
- etc...(他にもいろいろありますが本記事では割愛)
上から順番に説明していきます。
ジェネリクス
プロジェクトに参画後、環境構築が終わって意気揚々とソースコードを読み始めた私は、<T>
や ComponentPropsWithRef<typeof Hoge>
のような見たことのない記法を目にして早速つまずきました。
情報を漁っても「型の抽象化」「再利用性の向上や型安全性の維持のため...」など難しい言葉が出てきてなかなか理解できなかったので、少しまとめてみます。
ジェネリクスってなに?
ジェネリクスとは、語弊を恐れずに簡略化すると「使われるときまで具体的な型を決めなくてもよい型の入れ物」です。日本語で総称型とも呼ばれています。
具体的なソースコードと一緒に読むほうがわかりやすいので、下記をご覧ください。
// 基本的なジェネリクスの書き方
type Something<T> = {
value: T
}
const var1: Something<string> = {
value: "hoge" // T というジェネリクスにstring型を指定したので、var1.valueはstring型
}
const var2: Something<number> = {
value: 1 // T というジェネリクスにnumber型を指定したので、var2.valueはnumber型
}
Somethingという型定義を利用するときに初めてstring
やnumber
といった型が決まります。
これによって、string
型のSomethingとnumber
型のSomethingをそれぞれ用意する手間が省けますね。
次に関数に対してジェネリクスを使った例も読んでみましょう。
// 引数に入れたものがそのまま返却される関数 (アロー関数の場合、引数の()の前にジェネリクスを書きます)
const func = <T>(args: T) => {
return {value: args }
}
// T に number[]を指定したので、引数にはnumber型の配列を指定しないと型エラーとなる
func<number[]>([1,2,3])
// また、関数のジェネリクスは暗黙的に型が解決される仕組みがあり、
// ジェネリクスを使ったからといって利用時に必ず型指定しなくてもよい
func("hogehoge") // 暗黙的にTはstring型とみなされる
func(false) // 暗黙的にTはboolean型とみなされる
いろいろな制約の付け方
ジェネリクスに初期値を与えることで、型の安全性を上げて決められた型が守られやすくできます。
type Fuga<T = string> = {
value: T,
};
const var1: Fuga = {
value: "Hello world", // 初期値として指定されたstring型なのでOK
};
const var2: Fuga = {
value: 1, // 初期値として指定されていないnumber型なのでエラー
};
const var3: Fuga<number> = {
value: 1, // ジェネリクスの T がnumber型で上書きされたのでOK
};
また、以下のようにextends
によって型制約をつけることもできます
type Piyo<T extends string> = { // T はstring型の性質をもつ型しか受けつけない
value: T;
};
const var1: Piyo<string> = { // string型なのでOK
value: "Hello world",
};
const var2: Piyo<"piyo"> = { // "piyo"というリテラル型はstring型を拡張したものなのでOK
value: "piyo",
};
const var3: Piyo<number> = { // number型はstring型の制約を満たしていないのでエラーとなる
value: 123,
};
実際の開発案件では、一つの型定義に複数のジェネリクスが使われていたり、バックエンド側(サーバ)から返ってくるレスポンスの型を次項で説明するgetServerSidePropsで受け取るpropsの型と一致させるために使っていたり、応用的な書き方もたくさんあります。
ただ上記で説明した基本的な書き方を最低限理解しておけば、それらの型の管理についてもある程度はキャッチアップしやすくなると思います。
getServerSideProps
Next.jsでは CSR(クライアントサイドレンダリング)2とSSR(サーバサイドレンダリング)3を使い分けることが可能であり、getServerSidePropsはSSRの実現のためにサーバ側で用意したデータや処理等をpropsとしてpageコンポーネントに渡してくれる関数、ということはなんとなくプロジェクト参画時に知っていました。
しかし、具体的な仕組みや書き方を理解できていなかったため、ソースコードを読むだけでもかなり苦労したのを覚えています。
具体的にどうやって使うの?
getServerSideProps を使用するうえで最低限知っておかなければいけないのは以下の4点です。
- getServerSideProps を使用したページへリクエストがある度に実行される
- getServerSidePropsという決められた関数名でないと動かない
- pagesの配下でしか使えない
- returnで返却するものは props というオブジェクトに入れる
( props 以外にも redirect や notFound など返却できるオブジェクトはある )
基本的な書き方は以下の通りです。
export const getServerSideProps = async () => {
const data = await getData() // APIやDBからのデータ取得処理など
return {
props: { data: data }, // 返却するものはpropsオブジェクトに入れておく
}
}
これで data に取得したデータが正しく入っていれば、pageコンポーネントが受け取る props から data にアクセスできます。ここまでシンプルに書かれたコードなら理解しやすいですね。
さらにTypeScript で getServerSideProps を使用する場合は、前項で触れたジェネリクスを活用し、以下のようにして返却される props の型を指定できます。
こうすることで、pageコンポーネントで受け取る props の型をより安全に管理できます。
import { GetServerSideProps } from "next";
// pageコンポーネントで受け取る propsの型を定義
type Props = {
value: string;
}
// nextが提供しているGetServerSidePropsという型はジェネリクスが使われているので型を上書きできる
export const getServerSideProps: GetServerSideProps<Props> = async () => {
const data = await getData() // APIやDBからのデータ取得処理など
return {
props: {
value: data.value // GetServerSideProps のジェネリクスで指定した型と渡されるpropsの型が一致する
},
}
}
もし余力があれば、getServerSideProps で引数として受けとって使うことのできる context
についても学んでおきましょう。
export const getServerSideProps = async (context) => {
// context を利用した処理
}
上記で引数として受けとっている context には多くの情報が入っています。
params:そのページのルーティングに関するパラメータが入っているオブジェクト
req:リクエストデータが入っているオブジェクト
res:レスポンスデータが入っているオブジェクト
query:クエリパラメータが入っているオブジェクト
...(数が多いので以下略)
例えば、http://localhost:3000/test?keyword=hoge
のような URL でアクセスした場合、以下のpages/test/index.tsx
では getServerSideProps 内でクエリパラメータの文字列を取得して使うことができます。
export const getServerSideProps = async (context) => {
// クエリパラメータの文字列を取得
const keyword = context.query.keyword;
return {
props: {keyword: keyword} // props.keyword で "hoge" にアクセスできる
}
}
上記で説明したgetServerSideProps は Next.js でSSRを実現するために必須の関数なので、Next.js を扱う案件にアサインされる場合は必ず理解しておいたほうがよさそうですね!
よく知らないライブラリやフレームワーク
開発案件に新たにアサインされるとき、(特にフロントエンド初心者の場合は)自分の知っているライブラリやフレームワークだけで構成された開発案件である可能性はほぼゼロだと思います。
私の場合、Zod や MSW といったワードを聞いたことすらなかったので、初日でググりまくりました。
(知名度が高くないライブラリは日本語のドキュメントが存在しないのが当たり前なので、日頃から英語のドキュメントを読むことに慣れておくのがオススメです)
公式ドキュメントを読む癖や習慣を身につける
中・大規模なシステム開発で使用されるようなライブラリやフレームワークには、その開発者もしくは組織によって作成・管理されている公式ドキュメントがあるはずです。
そして言うまでもなく、非関係者である不特定多数の個人が書いた解説記事やブログ等よりも、その技術に最も精通している人たちが作成した公式ドキュメントに記載されている情報のほうが質が高くて信頼できるはずです。
人間は厳しい道よりも楽な道を選びがちなので、ほとんどの新米フロントエンジニアは、専門用語だらけでお堅い文章表現が多くなりがちな公式ドキュメントより、「初心者向け」とか「丁寧に解説してみた」みたいなキーワードが含まれるブログの記事等をクリックしがちです。しかし、私の身近にいるつよつよエンジニアの先輩方はみんな真っ先に公式ドキュメントを読みにいきますし、SNS等でもそういった声は多いです。
- まずはとりあえず公式ドキュメントを読む
- わからなければ、もう少し噛み砕いて易しく説明しているブログの記事等を読んでみる
- それらを読んでなんとなく理解できたら、公式ドキュメントをあらためて読んでみる
よく知らないライブラリやフレームワークについて調べるときは、上記のような流れを意識すると公式ドキュメントを読む習慣が身につくし、自身の理解度の向上も感じられると、同じチームの先輩から教わりました。
その他、情報収集するときのtips
-
公式ドキュメントに最速でアクセスしたい時は、「(ググる対象の正式名)+ 半角スペース + docs」でググる
→ ZodやMSWなどを「zod official document」や「msw 公式ドキュメント」でググってもなかなか目的の公式ドキュメントがヒットしなかったが、先輩から教えてもらった通りに「zod docs」や「msw docs」のようにググるとほぼ100%公式ドキュメントが検索結果の最上部に出てくるようになった。 -
API リファレンスを利用すれば、効率よく知りたい情報を早引きできる
→ 特定のメソッドや構成要素の仕様についてなど、「知りたいこと」がはっきりと決まっている場合は、API や API Reference4 といった項目に優先的にアクセスしてサイト内検索をすることで、かなり効率的にお目当ての情報に辿りつける。
-
プロジェクトで使用しているバージョンを把握してから公式ドキュメントやソースコードを読む
→ せっかく公式ドキュメントを読んでも、プロジェクトで使用しているものと異なるバージョンの情報では仕様が古すぎたり新しすぎたりして参考にならない場合がある(GitHubで直接ソースコードを読む際も同様)。package.json5などを確認し、 使用中のバージョンに合った適切な情報を探すべし。
-
GitHubのIssuesを覗いてみる
→ 公式ドキュメントに書いてある仕様に合わない挙動が見られる場合は、そのライブラリ・フレームワークのバグである可能性もある。そんな時は公式ドキュメントから公式のGitHubにアクセスし、Issuesのタブから関係開発者同士の議論を読んでいくと、直面しているバグをまさに解決しようとしていたり解決法が開示されていて approve & merge 待ちだったりする。
おわりに
本記事にて備忘録としてまとめておきたかったことは、以上となります。
実務経験がほとんどない状態で TypeScript や Next.js を扱うモダン開発案件にアサインされ、最初は困惑と焦りでいっぱいの日々でした。しかし、一緒に参画してくださった心強い先輩方の手厚いサポートもあり、よほど複雑な画面でなければ1人でも実装できるくらいには成長できたかなと感じています(使用技術や開発メンバーの異なる他の開発案件にいくとどうなるかわかりませんが...)。今回の開発案件で学んだことを存分に活かし、フロントエンドエンジニアとして着実に成長していくために今後も学んだことは記事を書いてアウトプットしていければと思います。
誤字や脱字のご指摘はもちろん、まだ技術的な説明に至らない点も多いかと思うので「ここ間違ってますよ!正しくは〇〇です!」などのコメント等あれば、是非ともよろしくお願いします。
最後まで読んでいただき、ありがとうございました!!!
-
独学の範囲内でReactやNext.js、TypeScriptの学習経験は有り。 ↩
-
CSRはClient Side Renderingの略で、日本語に訳すとクライアント側でのレンダリングとなる。 ↩
-
SSRはServer Side Renderingの略で、日本語に訳すとサーバ側でのレンダリングとなる。 ↩
-
API とは Application Programming Interface の略であり、API リファレンスにはそのライブラリやフレームワークを使用して開発を行うために必要な仕様が詳細に書かれている。 ↩
-
npmやyarn等のJavaScriptのパッケージマネージャーがパッケージを定義するファイル ↩