React/Next.js まったくわからんかった人がなんとなく理解するまでの格闘録
はじめに
大学サークルでWEBアプリを作ることになり、せっかくだからモダンな開発がしたいなぁ...。という思いからReact/Next.jsを使ったWEBアプリを作りました。筆者はプリミティブなHTML/CSS/Javascript、あとRailsくらいしかWEBアプリ開発経験がなかったので、React/Next.jsでのアプリ開発はなかなか苦労しました。なので、個人的に難しかったところをまとめようと思います。
開発当初、Next.jsはReactの上位互換であるという認識から、Next.jsを使ったら良くね??と思っていたのですが、Reactが分かっていないとNext.jsぜんぜんわからん!ということになったので、Reactを使って開発...。するものの、やっぱりNext.jsを使ったほうがきれいに書けるわ!ということがあったため、結局Next.jsで開発したという経緯があります。なので、どんな理由でNext.jsで開発したのかというところもお伝えできればいいかなと思います。
コンポーネント
当初、Next.jsで開発していてぶち当たった壁が「コンポーネント」です。基本みたいなところに苦戦したので、理解を深めるため開発をReactに切り替えました。コンポーネントとは「UIを独立した再利用できる部品に分割すること」で、考えそのものは理解できるものの、いざ自分でコードを書いてみると慣れるまでが難しかったですね。特に、propsの受け渡しでこんがらがってしまうことが多かったと思います。以下のコードはコンポーネントを実際に使っているところです。必要ではありませんが、JsdocとpropTypesを使うと可読性があがるのでおすすめですよ!
コンポーネントを使用
/**
* モデル名を一覧表示する
* @module List
* @param {props} props
* @return {React.Component}
*/
export function List(props) {
List.propTypes = {
allModelNameData: PropTypes.object,
};
return (
<>
{
props.allModelNameData.map((object, index) =>{
// ここでListsコンポーネントを使ってます!
return <Lists key={index} modelNameData={object.params.pid} />;
})
}
</>
);
};
コンポーネントを定義
/**
* モデル名を表示する
* @module Lists
* @param {props} props
* @return {Component}
* コンポーネントの返り値の型名って何なのかわからんかった。教えてください...。
*/
export function Lists(props) {
// 開発中にpropsの中身が分からなくなってくるので、PropTypesを導入するのがおすすめ
Lists.propTypes = {
modelNameData: PropTypes.string,
};
return (
<div>
<Link href={{pathname: `/hogehoge/${props.modelNameData}`}}>
<a> {props.modelNameData} </a>
</Link>
</div>
);
};
で、コンポーネントの何がいいわけ?
コンポーネントを理解し、使ってみるとソースコード見やすくなって便利!と思いました。なぜ見やすくなるのか、簡潔に言ってしまえば「関心の局所化と静的な記述」にあると思います。
引用元:再帰の反復blog
プログラムを分割するための手引き: 関心の局所化と静的な記述
小さな部品に分割したときに、その部品やその周辺の小さな部分だけを見て容易に理解できるのが望ましい。一方、実際の時系列に沿った動的な変化・処理の流れを追うと、プログラムの広い範囲の理解が必要になる。また動的な流れ・変化よりも静的な関係・構造の方が人間にとって理解が容易。
だから「処理の流れ・状態の変更」を直接記述することから離れると、局所的な理解が容易になり機能の分割・合成もおこないやすくなる。
静的ページの作成
コンポーネントがある程度理解できるようになったので、Reactだけでなんでも作れるじゃん!Next.jsいらねー!と思っていたのですが、ここで新たな壁にぶつかります。というのも、私の場合はファイルシステムで管理しているmdファイルをfsモジュールで読み込み、その結果を表示するということがしたかったのですが、fsモジュールはブラウザ上にあるはずのないmdファイルを探そうとするので、読み込んでもエラーが出てしまいます。最初はなぜ読み込んでくれないのかわからなかったので、「はにゃ??????」という感じでしたが、原因がわかったので「じゃあ、Reactとnode.jsを一緒に動かしてAPIで通信すればいいじゃん」という考えに至りました。しかし、実際にやってみると結構面倒くさそうだったので、さらに調べてみるとgetStaticProps
というものに行き着きました。getStaticProps
はNext.jsで静的サイトを生成する際に外部データを読み込むことができるAPIです。また、動的ルーティングを使って静的サイトを作りたい場合にはgetStaticPath
というAPIを使うことで、事前にgetStaticPath
で指定したパスをもとに静的なファイルを生成することができるということも分かりました。そういうわけで、静的サイトでいろいろ作る場合にはNext.jsのほうが簡単そうだったので、やっぱりNext.jsでの開発に戻すことにしました。
/**
* @module getStaticPaths
* @return {object}
*/
export async function getStaticPaths() {
const paths = getAllModelNameData();
//事前ビルドしたいパスをpathsとして渡す
return {
paths,
fallback: false,
};
}
/**
* @module getStaticProps
* @param {object} params - ルーティング情報
* @return {object} - モデルのデータ
* getStaticPropsはサーバー側のビルド時に呼び出されるため、fsモジュールが使える
*/
export async function getStaticProps({params}) {
// get3dModelPropertyは、fs.readdirを使ってmdファイルなどを取得する関数
const modelPropertyData = await get3dModelProperty(params.pid);
return {
props: {
modelPropertyData,
},
};
}
/**
* ブラウザにシーンを表示
* @module ModelView
* @param {object} modelPropertyData
* @return {React.Component}
*/
export function ModelView({modelPropertyData}) {
// 省略
}
動的ルーティング時にパスパラメータを取得したときにundefinedになる問題
こちらも実装の際に苦戦したところです。動的ルーティングで生成した静的ページでは、ページのルーティング情報を取得するuseRouter
がundefinedを返却してしまうという問題が発生してしまいます。Qiitaの記事でかなり分かりやすく解説されているものがあるのでそちらを参照してほしいのですが、簡潔に言えば、ページがハイドレーションされる際にきちんとルーティング情報を取得してくれるので、useEffectを使って、ルーティングの情報が取得されたら再度レンダリングするという方法で解決することができます。
export function ModelView({modelPropertyData}) {
// ルーティング関連
const router = useRouter();
const [pid, setQuery] = useState();
useEffect(() => {
if (router.asPath !== router.route) {
setQuery(router.query.pid);
}
}, [router]);
// クエリパラメーター取得済
if (pid) {
return (
<>
{省略}
</>
);
}
// クエリパラメーター未取得
if (!pid) {
return (
'loading'
);
}
};
おわりに
ReactやNext.jsまったくわからん!という方はとりあえずコードを書いてみるといいと思います。とはいえ、何を作ったらいいかわからん!と思いますので、トラハックさんのエンジニア学習講座などを見てはいかがでしょうか?個人的には日本一わかりやすいReact入門【実践編】を見ながらチャットボットを作ったのがかなりよかったです。
あと、記事の執筆中に「静的サイト」がなんども「性的サイト」に変換されるのでけしからんかったですね。
Discussion