React.js (Next.js) の世界を知る 🗺
はじめに
やりたいこと
オシゴトで利用することになった React.js (Next.js) を取り巻く世界を知るために、様々なことをやっていく記録です。この辺りの技術スタックが気になっている(全部やるとは言っていない)。
- React.js / Next.js
- TypeScript
- React Hooks
- GraphQL (Apollo)
- Flex Layout / Grid Layout
- Atomic Design
- Redux
- Redux ToolKit
- BFF (Express, NestJS ...)
つい最近、モバイル(iOS & Swift)の世界から Web の世界にやって来て完全に浦島たろう状態 𓆉
けっぱります!
React.js の素振り
React.js の大まかな世界観の把握は、公式にある実践的なチュートリアルを参考にした。
また、公式ドキュメントもとても親切に書かかれているので、そちらも合わせて。
公式さんアリガトウ。
チュートリアルに挑戦
react-tutorial
React.js の素振りの続き
ウェブにあるサンプルを写経して実際に動かしてみる。
react-todolist
react-router
Next.js の素振り
ウェブにあるサンプルを写経して実際に動かしてみる。
アプリのURL
開発ブランチ
Next.js で簡単なアプリを作る
もうちょっと Next.js をさわってみる。
アプリのURL
開発ブランチ
参考記事
React.js + Apollo で簡単なアプリをつくってみる
GraphQL の理解を深めるために Apollo ライブラリを利用したサンプルアプリを写経して実際に動かしてみる。
React.js + Apollo アプリ
開発ブランチ
参考記事
React Hooks API の素振り
React Fooks API の理解を深めるために、Web にあるサンプルを元に動かしてみる。
素振りで取り扱う React Hooks API
- 基本のフック
- useState
- useEffect
- useContext
- 追加のフック
- useReducer
- useCallback
- useMemo
- useRef
参考記事
開発ブランチ
Hooks API のメモ
useState
useState
は、関数コンポーネントで state を管理(state の保持と更新)するための React フック。唯一の引数は state の初期値。
基本形
const [count, setCount] = useState(initialState)
useEffect
useEffect
は、第1引数に渡された関数を描画の結果が画面に反映された後に動作させる React フック。第2引数「依存配列」を指定すると、依存配列に変更があった場合にのみ、第1引数で指定した関数が実行される。
基本形
useEffect(() => {
/* 第1引数には実行させたい副作用関数を記述 */
}, [依存する変数の配列]) // 第2引数には副作用関数の実行タイミングを制御する依存データを記述
useContext
useContext
は、既存の機能「Context」(a.k.a: データをアプリケーション内の深くまで渡す機能、pops のバケツリレーをしないで済む機能)をよりシンプルに分かりやすく書ける React フック。
基本形
const NumberContext = React.createContext()
const value = useContext(NumberContext)
useReducer
useReducer
は、状態管理のための React フックで、useState
と似たような機能がある。
useState
は useReducer
に内部実装されており、 useReducer
は複数の state を管理するのに適している。
基本形
const [state, dispatch] = useReducer(reducer, '初期値')
useCallback (+ React.memo)
useCallback
は、メモ化したコールバック関数を返す React フックで、パフォーマンス向上に利用される。
React.js では親コンポーネントが再描画されると子コンポーネントも一緒に再描画される。再描画が繰り返されると、描画の数だけパフォーマンスなどに影響がある。
そこで React.memo
を利用して、再描画の前に等価性のチェックを行い、必要な場合にのみ再描画をする手法がある。
それと併用されるフック関数が useCallback
である。
useCallback
でメモ化されたコールバック関数は、React.memo
でメモ化されたコンポーネントへ渡すことで不要な再描画をスキップ出来るようになる。
基本形
const callback = useCallback(関数, [deps])
useMemo
useMemo
は、関数の結果を保持するための React フックで、何度行っても結果が同じ場合の値などを保存(メモ化)し、そこから値を再取得する。
useMemo
の第2引数「依存配列」の値に変化がない場合は、キャッシュから値を取得する。
基本形
const sampleMemoFunc = () => {
const memoResult = useMemo(() => hogeMemoFunc(), [])
return <div>{memoResult}</div>
}
useRef
useRef
は、関数コンポーネントの参照を行う React フック。ちなみに、クラスコンポーネント参照の場合は ref 属性を利用して要素を参照する。
useState
の場合は state
が変更される度にコンポーネントの再描画が発生するが、useRef
は値が変更になっても、コンポーネントの再描画は発生しない特徴がある。
基本形
const refObject = useRef(initialValue)
const number = useRef(100)
console.log(number.current) // 100
GraphQL API をもっと理解する
GraphQL API をさらに手に馴染ませるために GitHub が公開している「GraphiQL」でいろいろ試す。
今回の教材
Memorandum
- REST API はマクドナルド方式(材料やその量が決まっている、変更できない、仕様がガチガチ)、GraphQL API はサブウェイ方式(個人の好みに応じて材料の量などを変更することができる)
- GraphQL API の「Request Method」は必ず
POST
になる - Prettify は
query
文字や不要なコードなどを削除する- ※ Operation Name はその限りではない
- Operation Name は「Request Payload」に付与される
- データの取得は「Query」を、データの更新・削除は「Mutation」を利用する
Aliases
同じフィールドに対して処理を行いたい場合、API で定義されているフィールドを複数使用すると構文エラーが発生する。その場合はフィールド名に別名(Aliases:エイリアス)を付与することで回避できる。
user1: user(login: "maecha") {
bio
login
avatarUrl
}
user2: user(login: "gipcompany") {
bio
login
avatarUrl
}
Fragments
上の Aliases の記述は重複したコードがあり冗長である。Fragments を利用するともっといい感じに書くことができる。フィールドへは展開構文で適用する。
{
user1: user(login: "maecha") {
...commonFields
}
user2: user(login: "gipcompany") {
...commonFields
}
}
fragment commonFields on User {
bio
login
avatarUrl
}
Operation Name
query
を識別するための機能。
query getUser1 {
user(login: "maecha") {
bio
}
}
query getUser2 {
user(login: "gipcompany") {
bio
}
}
Variables
動的な引数(変数)の使い方。query ()
に対応する引数を指定する。
query ($login: String!) {
user(login: $login) {
bio
name
}
}
// Query Variables
{
"login": "maecha"
}
Mutation
データの更新・削除は Mutation を利用する。
query repository {
repository(owner: "maecha", name: "react-hooks-examples") {
id
name
url
}
}
mutation addStar {
addStar(input: {
starrableId: "xxxxxxxxxxx"
}) {
starrable {
id
viewerHasStarred
}
}
}
mutation removeStar {
removeStar(input: {
starrableId: "xxxxxxxxxxx"
}) {
starrable {
id
viewerHasStarred
}
}
}
Inline Fragments + __typename
API から返却されるデータの型を動的に指定する。
query searchWithTypeName {
search(query: "We work hard", type: USER, first: 2) {
nodes {
__typename
... on User {
id
name
url
}
... on Organization {
id
name
url
projectsUrl
}
}
}
}
Type System, Scalar Types, Object Types, Schemas
データの型について。API 作成時に意識する部分。
Scalar Types の確認
Object Types の確認
Pagination (The Relay-Style Cursor Pagination)
The Relay-Style Cursor Pagination は、GraphQL を用いたページネーションでメジャーな実装スタイルの一つ。GitHub ではユーザーが検索した際のページネーションに利用されている。
図で確認
via: https://qiita.com/gipcompany/items/ffee8cf0b1522a741e12
via: https://qiita.com/gipcompany/items/ffee8cf0b1522a741e12
登場人物
- Connection:ある塊単位でデータを要求するその「ある塊」のこと
-
Page:Edges と pageInfo の2つで構成された Connection が示す情報の集まり
- Edge:位置情報を示す Cursor と情報そのモノである Node(データを抽象化した呼び名)で構成されている
-
pageinfo:startCursor、endCursor、hasPreviousPage、hasNextPage で構成されている
- startCursor:先頭の Edge がもつ Cursor(位置)情報
- endCursor:最後尾の edge がもつ Cursor(位置)情報
- hasPreviousPage:前のページが存在するかどうか
- hasNextPage:次のページが存在するかどうか
GraphiQL でレポジトリ検索
query searchRepository(
$first: Int,
$last: Int,
$after: String,
$before: String,
$query: String!
) {
search(
first: $first,
last: $last,
after: $after,
before: $before,
query: $query,
type: REPOSITORY
) {
repositoryCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
... on Repository {
id
name
url
}
}
}
}
}
// Query Variables
{
"after": null,
"before": null,
"first": 5,
"last": null,
"query": "フロントエンドエンジニア"
}
参考記事
作成したアプリ
今回の教材で作成したものは「GitHub レポジトリ検索アプリ」で、サーバーとのデータのやり取りは GraphQL を介して行っている。
アプリの機能はレポジトリの検索、スターの追加・削除、ページネーションの 3 つ。アプリの内部では Apollo Client の「writeQuery」や React Refs を利用している。
いったん Close にする。
Next.js の素振りをもう一度
これまで生の React.js を触る機会がほとんどで Next.js はあまり触れてこなかったので、もう一度このフレームワーク界隈を調査したりしていく。
手始めの読みもの
基礎的なことのインプットは、読みやすそうなサードパーティ記事と聖書で。
聖書に関しては、有志の方々が運営している日本語翻訳版もある。有り難やあ🙏
まずはこのあたりを読んで「完全に理解した」と言いたい。
翻訳版の内容で理解しておくと良いことありそうな項目
- プリレンダリング
- データ取得
- 画像最適化
- ルーティング
- API ルート
- ミドルウェア
- React 18
翻訳は完全にされていないので、原本も確認しながら読んでいく。
簡単なアプリを写経しながら手に馴染ませていく
Next.js(というか最近は React.js からも少しの間離れて、生の JavaScript を触っていた経緯もある)の全体像を俯瞰したい気持ちがあったので、簡単ばウェブアプリを写経して、世界観を感じたり、コードを手に馴染ませたりしていく作業を行った。
作成したアプリ
Zenn で人気のある教材「Next.js 14 アプリ開発のあらすじ」を使って作ったアプリをデプロイまでやってみた。
デモ
GitHub リポジトリ
このアプリの主な技術スタックはこちら。
-
Vercel
- ホスティング、DB(PostgreSQL)として利用
-
Cloudflare R2
- 画像サーバーとして利用
-
Clerk
- ユーザーの認証サービスとして利用
-
shadcn-ui/ui
- コンポーネントライブラリとして利用
-
Tailwind CSS
- CSS フレームワークとして利用
-
Prisma
- ORM として利用
shadcn-ui/ui
は初めて使うスタックで、利用するコンポーネントは npm でパッケージ化せず、CLI 経由でインストールしていく方式を採用している。コンポーネント自体は低レイヤーなもので構成されている。
各ページは pages/
ディレクトリを用意せず、app/
ディレクトリに直接パスと同じ名前のディレクトリを作成して、そこにファイルを置いていくスタイルを取っていた。これは App Router を採用しているからかなあ。
The App Router works in a new directory named app. The app directory works alongside the pages directory to allow for incremental adoption. This allows you to opt some routes of your application into the new behavior while keeping other routes in the pages directory for previous behavior. If your application uses the pages directory, please also see the Pages Router documentation.
インストールされたコンポーネントは components/ui
に配置される(配置場所は components.json
をいじったら変更できそう)。
インストールされたコンポーネントはこんな感じで呼べる。
import { Dialog } from "@/components/ui/dialog";
データ取得(サーバーとの通信とか)は /app/actions/
にまとめている。その中の lib.ts
で Prisma のラッパーを定義。
middleware.ts
で認証まわりのステータスの確認やリダイレクトの判定等をしている。
公式のチュートリアル
なかなか良さそう。
簡易ブログ(風な)アプリを作ってみた
ルーティングは App Router で実装。
記事一覧ページを ISR で、記事詳細ページを SSG でレンダリングしている。