React/Next.jsに入門してみる
モチベーションは以下
- いろいろOSSいじってみるとフロントエンドはReact多い
- フォームで日本語IMEの変換Enter考慮してないものがよくある
- フロントエンドさっぱりわからない
- ちょいちょいLLMに聞きながらPRしたりしてるけど、もうちょっと自信を持ってPRしたい
ということで、とりあえずここからやろうかと思ったんだけども、自分のJS/TS力が低すぎてぜんぜん理解できなかったのと、ハンズオン的に1からやれるものを期待していたので、ちょっと難しかった。
とりあえずKindle Unlimitedあるので、以下をやってみたけど、ざっくり流れがわかって良かった
- はじめてつくるReactアプリ: HTMLとCSSの知識だけで始められるReact開発
- はじめてつくるReactアプリ with TypeScript: たった2.5時間で「わかる! できる!」 最初に読みたかった本
※非Amazonアフィリエイトリンク
JS/TS両方で書いてみて、雰囲気掴めたのでもう一度公式のクイックスタートからチュートリアルにトライする。
書籍で一通りやったこともあり、クイックスタートに書いてある内容が理解できた。
やっぱり、最初に色々説明されるよりも、ハンズオン形式の中で自然と学ぶみたいなのが個人的には好みだなぁ。
ということで、チュートリアルをやっていく・・・
のだが、CodeSandboxが使いにくい。
- ターミナルどこ?
- React DevToolsどこ?
ほぼほぼVSCodeなんだけども微妙に違うってのが余計に面倒。CodeSandboxに慣れてないってのはあるけども。
ということで自分はローカルでやってみる。フレームワークはViteを使う(ビルドツールらしいが未だによくわからん)
DevContainerの設定ファイルを作成して、VS CodeでDevContainerを起動。
{
"name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
}
プロジェクトを作成
$ npm create vite@latest
✔ Project name: … tic-tac-toe
✔ Select a framework: › React
✔ Select a variant: › JavaScript
プロジェクトディレクトリに移動してパッケージインストール
$ cd tic-tac-toe
$ npm install
vite.config.jsを一部修正
export default defineConfig({
plugins: [react()],
// 以下を追加
server: {
host: '127.0.0.1',
port: 5173,
},
})
まずは動作確認。Viteの開発サーバを起動。
$ npm run dev
ブラウザでアクセスできれば確認OK。開発サーバをctrl+cで一旦止める。
要らないファイルを全部削除する
$ rm -rf src/App.css src/assets src/public
$ cat /dev/null > src/App.jsx
$ cat /dev/null > src/index.css
CSSだけは先に設定しておくと良い。CodeSandboxにあsytles.cssをそのままindex.cssにコピペ。
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
margin: 20px;
padding: 0;
}
h1 {
margin-top: 0;
font-size: 22px;
}
h2 {
margin-top: 0;
font-size: 20px;
}
h3 {
margin-top: 0;
font-size: 18px;
}
h4 {
margin-top: 0;
font-size: 16px;
}
h5 {
margin-top: 0;
font-size: 14px;
}
h6 {
margin-top: 0;
font-size: 12px;
}
code {
font-size: 1.2em;
}
ul {
padding-inline-start: 20px;
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
margin: 20px;
padding: 0;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.board-row:after {
clear: both;
content: '';
display: table;
}
.status {
margin-bottom: 10px;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
開発サーバを起動。真っ白なページが表示されているはず。
$ npm run dev
あとはこのままチュートリアルに従って、App.jsxを修正していいけばよいはず。
React Developer Toolsは以下から以下からインストール
とりあえずこんな感じで。コンポーネントごとにファイルも分割してみた。
import Board from './components/Board';
export default function App() {
return (
<Board />
);
}
import { useState } from 'react';
import Square from './Square';
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
const nextSquares = squares.slice();
nextSquares[i] = xIsNext ? "X" : "O";
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)}/>
<Square value={squares[1]} onSquareClick={() => handleClick(1)}/>
<Square value={squares[2]} onSquareClick={() => handleClick(2)}/>
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)}/>
<Square value={squares[4]} onSquareClick={() => handleClick(4)}/>
<Square value={squares[5]} onSquareClick={() => handleClick(5)}/>
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)}/>
<Square value={squares[7]} onSquareClick={() => handleClick(7)}/>
<Square value={squares[8]} onSquareClick={() => handleClick(8)}/>
</div>
</div>
)
}
export default Board;
import PropTypes from 'prop-types';
Square.propTypes = {
value: PropTypes.string,
onSquareClick: PropTypes.func.isRequired,
};
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default Square;
まだぜんぜんわかった感がないな、もっと色んなパターンを書くしかないな。
新しい言語を始める時、コードの理解はまあ普通に重要なんだけど、周辺のエコシステムを理解するほうがより重要で、そして大変なのよね、、、というのが個人的な意見。
次はこれ。
※非アフィリエイトリンク
わかりやすいのだが、Kindleアプリ(Mac)がもっさりしてて辛い。気軽に読むだけならそんなに気にならなかったのだけど、コードが多いとKindleはちょっとしんどいなぁ・・・途中からコードコピペみたいなのが多くなってから一気にしんどくなった。
公式チュートリアルがあるのだなー、ドキュメントから探せなかった。これに従ってやってみることにする。
とりあえずReactもまだまだなのでこちらから。
Chapter8完了でこんな感じ。
<html>
<body>
<div id="app"></div>
<!-- Reactを読み込む -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!-- React DOMを読み込む -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babelを使ってJSXをブラウザで実行できるようにする -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type='text/jsx'>
// HTMLのidが'app'の要素を取得
const app = document.getElementById('app');
// Headerコンポーネントを定義。titleというプロパティを受け取る
function Header({title}) {
// titleがあればtitleを表示、なければ'Default Title'を表示する
return <h1>{title ? title : 'Default Title'}</h1>;
}
// HomePageコンポーネントを定義
function HomePage() {
// 名前リストを定義
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
// likesの状態を管理する。初期値は0
const [likes, setLikes] = React.useState(0);
// ボタンクリック時にlikesを1増やす
function handleClick() {
setLikes(likes + 1);
}
// HomePageコンポーネントが返すJSX
return (
<div>
{/* Headerコンポーネントを表示。titleプロパティを渡す */}
<Header title="Develop. Preview. Ship."/>
<ul>
{/* namesリストをmapで展開してli要素を生成 */}
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
{/* ボタンクリック時にhandleClick関数を呼び出す */}
<button onClick={handleClick}>Like ({likes})</button>
</div>
)
}
// React DOMを使ってHomePageコンポーネントを'app'要素にレンダリングする
const root = ReactDOM.createRoot(app);
root.render(<HomePage />);
</script>
</body>
</html>
Chapter9
自分はdevcontainer環境でやった
{
"name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
}
devcontainerで開いて、パッケージインストール
$ npm install react@latest react-dom@latest next@latest
$ tree -L 1
.
├── index.html
├── node_modules
├── package.json
└── package-lock.json
2 directories, 3 files
index.htmlを削除して、以下のように修正。
import LikeButton from './like-button';
function Header({title}) {
return <h1>{title ? title : 'Default Title'}</h1>;
}
export default function HomePage() {
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
return (
<div>
<Header title="Develop. Preview. Ship."/>
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
<LikeButton />
</div>
)
}
// このコンポーネントがクライアントサイドでレンダリングされることを示
'use client';
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
function handleClick() {
setLikes(likes + 1);
}
return <button onClick={handleClick}>Like ({likes})</button>;
}
'use client';
の箇所の説明はChapter10にある
Next.jsの開発サーバを立ち上げる。
{
"scripts": {
"dev": "next dev"
},
"dependencies": {
"next": "^14.2.11",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
$ npm run dev
$ tree -L 2
.
├── app
│ ├── layout.js
│ ├── like-button.js
│ └── page.js
├── node_modules
│ (snip)
│ └── (snip)
├── package.json
└── package-lock.json
22 directories, 5 files
layout.js
は開発サーバ起動時に自動で作成される
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
スッキリ簡潔でよいチュートリアルだと思った。もうちょっと実践的なバリエーションが欲しくなるのではあるけども。
Next.jsのチュートリアル。16章で結構ボリュームある。
作るもの
*refered from https://nextjs.org/learn/dashboard-appこのコースでは、財務ダッシュボードの簡易版を構築します:
- 公開ホームページ
- ログインページ
- 認証により保護されたダッシュボードページ。
- ユーザが請求書を追加、編集、削除する機能。
ダッシュボードには、後の章でセットアップするデータベースも付随します。
コースの終わりには、フルスタックのNext.jsアプリケーションを作り始めるために必要な基本的なスキルが身につきます。
概要
このコースで学ぶ機能の概要です:
- スタイリング: Next.jsでアプリケーションをスタイリングするさまざまな方法。
- 最適化: 画像、リンク、フォントを最適化する方法。
- ルーティング: ファイルシステムのルーティングを使ってネストしたレイアウトやページを作成する方法。
- データ取得: Vercelでデータベースをセットアップする方法、取得とストリーミングのベストプラクティス。
- 検索とページネーション: URL Search Paramsを使って検索とページネーションを実装する方法。
- データの変異: React Server Actionsを使用してデータを変異させ、Next.jsキャッシュを再検証する方法。
- エラー処理: 一般的なエラーと
404
not foundエラーを処理する方法。- フォームバリデーションとアクセシビリティ: サーバーサイドのフォームバリデーションのやり方と、アクセシビリティを向上させるためのヒント。
- 認証:
NextAuth.js
とミドルウェアを使ってアプリケーションに認証を追加する方法。- メタデータ:メタデータを追加し、ソーシャル共有のためにアプリケーションを準備する方法。
作業ディレクトリ作成
$ mkdir nextjs-tutorial-dashboard && nextjs-tutorial-dashboard
いつも通りDevContainer出やりたかったのだけど、create-next-appでファイルシステムまわりのエラーが出てしまう(デフォルトでbind mountしてるからじゃなかろうか)ので、今回はローカルのMac上でやる。
プロジェクト作成
$ npm install -g pnpm
$ npx create-next-app@latest nextjs-dashboard --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" --use-pnpm
nextjs-dashboard
ディレクトリができるのでVSCodeなどで開く
$ cd nextjs-dashboard
$ code .
ディレクトリ構造はこんな感じ。ちょっとチュートリアルとは一部異なっている。
$ tree
.
├── README.md
├── app
│ ├── layout.tsx
│ ├── lib
│ │ ├── data.ts
│ │ ├── definitions.ts
│ │ ├── placeholder-data.ts
│ │ └── utils.ts
│ ├── page.tsx
│ ├── seed
│ │ └── route.ts
│ └── ui
│ ├── acme-logo.tsx
│ ├── button.tsx
│ ├── customers
│ │ └── table.tsx
│ ├── dashboard
│ │ ├── cards.tsx
│ │ ├── latest-invoices.tsx
│ │ ├── nav-links.tsx
│ │ ├── revenue-chart.tsx
│ │ └── sidenav.tsx
│ ├── global.css
│ ├── invoices
│ │ ├── breadcrumbs.tsx
│ │ ├── buttons.tsx
│ │ ├── create-form.tsx
│ │ ├── edit-form.tsx
│ │ ├── pagination.tsx
│ │ ├── status.tsx
│ │ └── table.tsx
│ ├── login-form.tsx
│ ├── search.tsx
│ └── skeletons.tsx
├── next-env.d.ts
├── next.config.mjs
├── node_modules
│ (snip)
│ ├── (snip)
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
│ ├── customers
│ │ ├── amy-burns.png
│ │ ├── balazs-orban.png
│ │ ├── delba-de-oliveira.png
│ │ ├── evil-rabbit.png
│ │ ├── lee-robinson.png
│ │ └── michael-novotny.png
│ ├── favicon.ico
│ ├── hero-desktop.png
│ ├── hero-mobile.png
│ └── opengraph-image.png
├── tailwind.config.ts
└── tsconfig.json
34 directories, 44 files
パッケージをインストール
$ pnpm i
開発サーバを起動
$ pnpm dev
あとはチュートリアルに従って進める。以下の記事でも細かくステップごとに説明があるので参考になる。
最終的にこんな感じになる。
とても良くできたチュートリアルではあるのだけど、ボリュームもでかいし、自分の場合は新しく学ぶことが多すぎて、ちょっとお腹いっぱいになってしまった。もう一度同じものを作れ、と言われても絶対できない自信がある。
もう少し小さめのものを複数やって慣れる必要がありそう。
内容的には少し古いみたいなのだけど、これかなぁ
新しい書き方で解説されている方もいる
まあコツコツやる