Closed9

React/Next.jsに入門してみる

kun432kun432

モチベーションは以下

  • いろいろOSSいじってみるとフロントエンドはReact多い
  • フォームで日本語IMEの変換Enter考慮してないものがよくある
  • フロントエンドさっぱりわからない
  • ちょいちょいLLMに聞きながらPRしたりしてるけど、もうちょっと自信を持ってPRしたい

ということで、とりあえずここからやろうかと思ったんだけども、自分のJS/TS力が低すぎてぜんぜん理解できなかったのと、ハンズオン的に1からやれるものを期待していたので、ちょっと難しかった。

https://ja.react.dev/learn

とりあえずKindle Unlimitedあるので、以下をやってみたけど、ざっくり流れがわかって良かった

※非Amazonアフィリエイトリンク

JS/TS両方で書いてみて、雰囲気掴めたのでもう一度公式のクイックスタートからチュートリアルにトライする。

kun432kun432

書籍で一通りやったこともあり、クイックスタートに書いてある内容が理解できた。

https://ja.react.dev/learn

やっぱり、最初に色々説明されるよりも、ハンズオン形式の中で自然と学ぶみたいなのが個人的には好みだなぁ。

ということで、チュートリアルをやっていく・・・

https://ja.react.dev/learn/tutorial-tic-tac-toe

のだが、CodeSandboxが使いにくい。

  • ターミナルどこ?
  • React DevToolsどこ?

ほぼほぼVSCodeなんだけども微妙に違うってのが余計に面倒。CodeSandboxに慣れてないってのはあるけども。

ということで自分はローカルでやってみる。フレームワークはViteを使う(ビルドツールらしいが未だによくわからん)

DevContainerの設定ファイルを作成して、VS CodeでDevContainerを起動。

.devcontainer/devcontainer.json
{
	"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を一部修正

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にコピペ。

src/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は以下から以下からインストール
https://chromewebstore.google.com/detail/fmkadmapgofadopljbjfkapdkoienihi?hl=ja

kun432kun432

とりあえずこんな感じで。コンポーネントごとにファイルも分割してみた。

src/App.jsx
import Board from './components/Board';

export default function App() {
  return (
    <Board />
  );
}
src/components/Board.jsx
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;
src/components/Square.jsx
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;

まだぜんぜんわかった感がないな、もっと色んなパターンを書くしかないな。

kun432kun432

新しい言語を始める時、コードの理解はまあ普通に重要なんだけど、周辺のエコシステムを理解するほうがより重要で、そして大変なのよね、、、というのが個人的な意見。

kun432kun432

次はこれ。

※非アフィリエイトリンク

わかりやすいのだが、Kindleアプリ(Mac)がもっさりしてて辛い。気軽に読むだけならそんなに気にならなかったのだけど、コードが多いとKindleはちょっとしんどいなぁ・・・途中からコードコピペみたいなのが多くなってから一気にしんどくなった。

公式チュートリアルがあるのだなー、ドキュメントから探せなかった。これに従ってやってみることにする。

https://zenn.dev/ulxsth/scraps/316bbbdabede5f

kun432kun432

とりあえずReactもまだまだなのでこちらから。

https://nextjs.org/learn/react-foundations

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環境でやった

.devcontainer/devcontainer.json
{
	"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を削除して、以下のように修正。

app/page.js
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>
    )
}
app/like-button.js
// このコンポーネントがクライアントサイドでレンダリングされることを示
'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にある

https://nextjs.org/learn/react-foundations/server-and-client-components

Next.jsの開発サーバを立ち上げる。

package.json
{
  "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は開発サーバ起動時に自動で作成される

app/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>
  )
}

スッキリ簡潔でよいチュートリアルだと思った。もうちょっと実践的なバリエーションが欲しくなるのではあるけども。

kun432kun432

Next.jsのチュートリアル。16章で結構ボリュームある。

https://nextjs.org/learn/dashboard-app

作るもの


*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 とミドルウェアを使ってアプリケーションに認証を追加する方法。
  • メタデータ:メタデータを追加し、ソーシャル共有のためにアプリケーションを準備する方法。
kun432kun432

作業ディレクトリ作成

$ 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

あとはチュートリアルに従って進める。以下の記事でも細かくステップごとに説明があるので参考になる。

https://zenn.dev/ulxsth/scraps/316bbbdabede5f

最終的にこんな感じになる。

とても良くできたチュートリアルではあるのだけど、ボリュームもでかいし、自分の場合は新しく学ぶことが多すぎて、ちょっとお腹いっぱいになってしまった。もう一度同じものを作れ、と言われても絶対できない自信がある。

もう少し小さめのものを複数やって慣れる必要がありそう。

このスクラップは2ヶ月前にクローズされました