デザインについての学習メモブログ

React入門 #03 – 初めてのReactアプリ作成とプロジェクト構成の理解

記事内に広告が含まれています。

React入門 #03 – 初めてのReactアプリ作成とプロジェクト構成の理解

前回までで開発環境を整えました。

この記事では、実際にReactアプリを作成し、プロジェクトの構成を詳しく理解していきます。

各ファイルの役割を知ることで、今後の開発がスムーズになります。

JavaScript版とTypeScript版の両方記載しています。

学習したい方で実際にコードを動かしながら学習が進められます。

1. プロジェクトの作成

Reactの学習を始めるには、まず開発環境を整える必要があります。

ここでは、Viteという高速なビルドツールを使って、数分で動くReactアプリを作成します。

JavaScriptとTypeScriptの両方の手順を紹介しますので、自分に合った方を選んで進めてください。

Viteでプロジェクト作成

JavaScript版:

Bash
npm create vite@latest my-first-app -- --template react
cd my-first-app
npm install
npm run dev

TypeScript版:

Bash
npm create vite@latest my-first-app-ts -- --template react-ts
cd my-first-app-ts
npm install
npm run dev

npm createで何度か入力を聞いてきますが、今回は、全て「エンターボタンを押す」で良いです。

ブラウザで http://localhost:5173 を開くと、Viteのデフォルト画面が表示されます。

💡 Point

“– –template react”はViteなどのビルドツールでプロジェクトを作成する際に使用するオプションです。

2. プロジェクト構成の全体像

プロジェクトを作成すると、たくさんのファイルやフォルダが自動生成されます。

最初は「これは何?」と戸惑うかもしれませんが、各ファイルには明確な役割があります。

全体像を把握することで、どこに何を書けば良いのかが分かるようになり、開発がスムーズに進みます。

JavaScript版の構成

Bash
my-first-app/
├── node_modules/          # インストールされたパッケージ(触らない)
├── public/                # 静的ファイル
   └── vite.svg          # ファビコン
├── src/                   # ソースコード(メイン作業場所)
   ├── assets/           # 画像などのアセット
      └── react.svg
   ├── App.css           # Appコンポーネントのスタイル
   ├── App.jsx           # メインコンポーネント
   ├── index.css         # グローバルスタイル
   └── main.jsx          # エントリーポイント
├── .gitignore            # Gitで無視するファイル
├── index.html            # HTMLテンプレート
├── package.json          # プロジェクト設定
├── package-lock.json     # 依存関係のロックファイル
└── vite.config.js        # Viteの設定ファイル

TypeScript版の構成

Bash
my-first-app-ts/
├── node_modules/
├── public/
   └── vite.svg
├── src/
   ├── assets/
      └── react.svg
   ├── App.css
   ├── App.tsx           # .tsx拡張子
   ├── index.css
   └── main.tsx          # .tsx拡張子
├── .gitignore
├── index.html
├── package.json
├── package-lock.json
├── tsconfig.json         # TypeScript設定(参照用)
├── tsconfig.app.json     # アプリ用TypeScript設定
├── tsconfig.node.json    # Node/Vite用TypeScript設定
└── vite.config.ts        # .ts拡張子

以前のバージョンではsrc/vite-env.d.tsというファイルがありましたが、最新版ではtsconfig.app.jsontypes設定で型定義を指定する方式に変更されています。

JSON
"types": ["vite/client"],

つまり、最新版のViteでは`vite-env.d.ts`ファイルは不要になり、代わりに`tsconfig.app.json`の`types`配列で型定義を指定する方式に変わったということですね。

3. 各ファイルの詳細解説

プロジェクトの構成が分かったら、次は個々のファイルの中身を理解しましょう。

index.htmlmain.jsxApp.jsxなど、それぞれのファイルがどんな役割を担っているのか、コードの意味は何なのかを一つずつ丁寧に解説します。

ここを理解すれば、Reactアプリの動作原理が見えてきます。

JavaScriptとTypeScriptの主な違い:

プロジェクトを作成する際、JavaScriptとTypeScriptのどちらかを選択しましたが、両者にはいくつかの違いがあります。

ファイル拡張子

  • JavaScript版:.js(通常のJS)、.jsx(JSXを含むファイル)
  • TypeScript版:.ts(通常のTS)、.tsx(JSXを含むファイル)

設定ファイル

  • JavaScript版:vite.config.jsのみ
  • TypeScript版:vite.config.tstsconfig.jsontsconfig.node.jsontsconfig.app.jsonなどが追加

型定義ファイル

TypeScript版のみ:src/vite-env.d.ts(Viteの型定義)

*現在は別の場所から参照しているので不要

コード内の違い

  • TypeScript版では型注釈(: string: numberなど)が付く
  • 関数の戻り値の型(: void: JSX.Elementなど)を明示

これらの違いを意識しながら、以下で各ファイルを詳しく見ていきましょう。

index.html – HTMLテンプレート

HTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>my-first-app</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

重要なポイント:

  • <div id="root"></div>:
    Reactアプリがマウントされる(作成したアプリの要素が埋め込まれるイメージ)場所
  • <script type="module" src="/src/main.jsx"></script>:
    エントリーポイントの読み込み
  • このファイルは通常ほとんど編集しません。
    このページにアクセスする事でプログラムが実行されるキッカケ(エントリーポイントが読み込まれる)を作っていると考えてください。

main.jsx / main.tsx – エントリーポイント

JavaScript版(main.jsx):

JSX
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

TypeScript版(main.tsx):

TSX
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

各部分の説明:

1. import文

JSX
import { StrictMode } from 'react' // React本体からStrictModeコンポーネントをインポート
import { createRoot } from 'react-dom/client'  // DOM操作用 createRoot関数をインポート
import App from './App.tsx'         // メインコンポーネント
import './index.css'                // グローバルスタイル

2. createRoot()

JSX
createRoot(document.getElementById('root'))
  • index.html<div id="root">を取得
  • React 18で導入された新しいAPI
  • TypeScript版の「!」は”非Null アサーション演算子”で「この要素は必ず存在する」という意味

3. render()

JSX
.render(
  <StrictMode>
    <App />
  </StrictMode>,
)
  • <App />コンポーネントをレンダリング(つまり、’root’要素の中にApp要素の内容が描画される)
  • <StrictMode>は開発時に問題を検出するためのツール

💡 StrictModeとは?

  • 開発モードでのみ動作(本番環境では影響なし)
  • 非推奨のAPIを使っていないかチェック
  • 副作用(useEffectなど)が2回実行される(意図的な動作)
  • 初心者は「デバッグ用のモード」と理解すればOK

App.jsx / App.tsx – メインコンポーネント

デフォルトのコードを見てみましょう(簡略化):

JavaScript版(App.jsx):

JSX
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

TypeScript版(App.tsx):

TSX
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

’function App() {’ か ’function App(): JSX.Element {’ どっち?

新しいバージョンでは、’function App() {’となっています。

型推論により、明示的に書かなくてもTypeScriptが自動的に型をチェックしているようです。

コードの解説:

1. useState – 状態管理

JSX
const [count, setCount] = useState(0)
  • count: 現在の値
  • setCount: 値を更新するための関数
  • useState(0): 初期値は0

2. JSXの構造

JSX
<>  {/* フラグメント */}
  <div>...</div>
  <h1>...</h1>
</>

3. イベントハンドラ

JSX
<button onClick={() => setCount((count) => count + 1)}>
  • クリック時にcountを1増やす
  • (count) => count + 1は関数形式の更新
  • setCountでcountを新しい値に更新

package.json – プロジェクト設定

JSON
{
  "name": "my-first-app-ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@types/node": "^24.10.1",
    "@types/react": "^19.2.5",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "typescript": "~5.9.3",
    "typescript-eslint": "^8.46.4",
    "vite": "^7.2.4"
  }
}

JavaScript版は、typescript関連が無い

主要なスクリプト:

  • npm run dev: 開発サーバー起動
  • npm run build: 本番用ビルド
  • npm run preview: ビルド結果のプレビュー

vite.config.js / vite.config.ts – Vite設定

JavaScript版(vite.config.js):

JavaScript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

TypeScript版(vite.config.ts):

TypeScript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

カスタマイズ例:

JavaScript
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,  // ポート変更
    open: true,  // 自動でブラウザを開く
  },
  build: {
    outDir: 'dist',  // ビルド出力先
  },
})

tsconfig.app.json – TypeScript設定(TS版のみ)

JSON
{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "types": ["vite/client"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": ["src"]
}

重要な設定項目:

  • jsx: "react-jsx": JSXの変換方法
  • strict: true: 厳格な型チェック
  • noUnusedLocals: true: 未使用変数を警告

初心者向けの緩い設定:

JSON
{
  "compilerOptions": {
    "strict": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false
  }
}

4. 初めてのカスタマイズ

ファイルの役割が理解できたら、実際に手を動かしてコードを書き換えてみましょう。

デフォルトのコードをシンプルなカウンターアプリに変更することで、Reactの基本的な書き方や状態管理の仕組みを体感できます。

自分で書いたコードが動く喜びを味わってください。

シンプルなカウンターアプリ

JavaScript版(App.jsx):

JSX
import { useState } from 'react'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }

  const decrement = () => {
    setCount(count - 1)
  }

  const reset = () => {
    setCount(0)
  }

  return (
    <div className="app">
      <h1>カウンターアプリ</h1>
      <div className="counter">
        <p className="count-display">{count}</p>
        <div className="button-group">
          <button onClick={decrement}>-</button>
          <button onClick={reset}>リセット</button>
          <button onClick={increment}>+</button>
        </div>
      </div>
    </div>
  )
}

export default App

TypeScript版(App.tsx):

TSX
import { useState, type JSX } from 'react'
import './App.css'

function App(): JSX.Element {
  const [count, setCount] = useState<number>(0)

  const increment = (): void => {
    setCount(count + 1)
  }

  const decrement = (): void => {
    setCount(count - 1)
  }

  const reset = (): void => {
    setCount(0)
  }

  return (
    <div className="app">
      <h1>カウンターアプリ</h1>
      <div className="counter">
        <p className="count-display">{count}</p>
        <div className="button-group">
          <button onClick={decrement}>-</button>
          <button onClick={reset}>リセット</button>
          <button onClick={increment}>+</button>
        </div>
      </div>
    </div>
  )
}

export default App

スタイル(App.css):

CSS
.app {
  text-align: center;
  padding: 2rem;
}

.counter {
  margin: 2rem auto;
  max-width: 400px;
  padding: 2rem;
  border: 2px solid #646cff;
  border-radius: 8px;
  background-color: #1a1a1a;
}

.count-display {
  font-size: 4rem;
  font-weight: bold;
  margin: 1rem 0;
  color: #646cff;
}

.button-group {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

button {
  padding: 0.8rem 1.5rem;
  font-size: 1.2rem;
  border-radius: 8px;
  border: 1px solid transparent;
  background-color: #646cff;
  color: white;
  cursor: pointer;
  transition: all 0.3s;
}

button:hover {
  background-color: #535bf2;
  transform: translateY(-2px);
}

button:active {
  transform: translateY(0);
}

実行と確認

Bash
npm run dev

5. publicフォルダとassetsフォルダの違い

Reactプロジェクトには画像などのファイルを配置する場所が2つあります。

publicフォルダとsrc/assetsフォルダです。この2つは似ているようで全く異なる役割を持っています。

どちらに何を置くべきか理解することで、適切なファイル管理ができるようになります。

publicフォルダ

公開ファイルの保存先です。直接アクセスさせたいファイルはこちらに保存する

Bash
public/
└── vite.svg

特徴:

  • ビルド時にそのままビルド先へコピーされる
  • URLで直接アクセス可能
  • ファイル名がハッシュ化されない(ブラウザーのキャッシュによって最新画像に変わらない場合がある)

使用例:

HTML
<!-- index.htmlから参照 -->
<link rel="icon" type="image/svg+xml" href="/vite.svg" />

適している用途:

  • ファビコン
  • robots.txt
  • manifest.json
  • 固定のアセットファイル

src/assetsフォルダ

Bash
src/assets/
└── react.svg

特徴:

  • Viteのビルドシステムで処理される
  • import文で読み込む
  • ファイル名がハッシュ化される(キャッシュ対策)
  • 最適化される

使用例:

JSX
import logo from './assets/react.svg'

function App() {
  return <img src={logo} alt="Logo" />
}

適している用途:

  • コンポーネント内で使う画像
  • CSSで使う画像
  • 動的に読み込む画像

6. Hot Module Replacement (HMR)

Viteを使った開発で最も便利な機能の一つが「HMR(ホットモジュールリプレースメント)」です。

コードを保存するだけで、ブラウザが自動的に更新され、しかもアプリの状態(例えばカウンターの数値)は保持されたままです。

この機能により、開発効率が劇的に向上します。

HMRとは?

  • コードを保存すると、ページ全体をリロードせずに変更が反映される
  • アプリの状態を保ったまま更新できる
  • 開発効率が大幅に向上

試してみよう:

  • npm run devでサーバーを起動
  • App.jsxの内容を編集
  • 保存すると即座にブラウザに反映される
  • カウンターの値もリセットされない(状態が保持される)

7. ビルドと本番環境

開発中はnpm run devで開発サーバーを起動していますが、実際にWebサイトを公開する際には「ビルド」という作業が必要です。

ビルドすることで、コードが最適化され、読み込みが高速になります。

ここでは開発環境と本番環境の違いと、ビルドの方法について学びます。

開発用ビルド

Bash
npm run dev
  • ソースマップ付き
  • 詳細なエラーメッセージ
  • HMR有効
  • ファイルサイズは大きい

本番用ビルド

Bash
npm run build
  • コードが圧縮(minify)される
  • 不要なコードが削除される
  • ファイルサイズが最適化される
  • distフォルダに出力される

ビルド結果の確認:

Bash
npm run preview

本番環境と同じ状態でローカルで確認できます。

distフォルダの構造

Bash
dist/
├── assets/
   ├── index-abc123.css    # ハッシュ付きファイル名
   └── index-def456.js
├── vite.svg
└── index.html

このフォルダをサーバーにデプロイすれば公開できます。

8. よくある初心者の疑問

Reactを学び始めると、「なぜこう書くの?」「これは必要なの?」といった疑問が次々と出てきます。

ここでは、初心者がつまずきやすいポイントや、よく聞かれる質問について分かりやすく解説します。

理解が深まれば、自信を持ってコードを書けるようになります。

Q1: なぜ.jsxや.tsxを使うの?

A: JSXが含まれるファイルであることを明示するためです。

  • .js / .ts: 通常のJavaScript/TypeScript
  • .jsx / .tsx: JSXを含むファイル

Viteでは.jsx/.tsxを使わないとエラーになります。

Q2: import ‘./App.css’の意味は?

A: CSSファイルをJavaScriptにインポートしています。

  • Viteが自動的に<style>タグとして埋め込む
  • モジュール化されたCSSとして扱える

Q3: なぜexport defaultを使うの?

A: コンポーネントをファイル外(別のファイル)で使えるようにするためです。.

JSX
// App.jsx
function App() { ... }
export default App  // これで他のファイルから使える

// main.jsx
import App from './App.jsx'  // インポートできる

Q4: React.StrictModeは必要?

A: 開発時のバグ検出に役立ちますが、必須ではありません。

  • 本番環境では影響しない
  • 初心者は気にしなくてOK
  • 外しても問題なし

9. 実践:Todoアプリの基礎

ここまで学んだ知識を活かして、実際に動くアプリを作ってみましょう。

シンプルなTodoアプリの骨組みを作成することで、useState、イベント処理、リストのレンダリングなど、Reactの基本的な概念を実践的に理解できます。

まずは動くものを作ることが、学習の最良の方法です。

JavaScript版(App.jsx):

JSX
import { useState } from 'react'
import './App.css'

function App() {
  const [todos, setTodos] = useState([])
  const [inputValue, setInputValue] = useState('')

  const addTodo = () => {
    if (inputValue.trim()) {
      setTodos([...todos, inputValue])
      setInputValue('')
    }
  }

  return (
    <div className="app">
      <h1>シンプルTodo</h1>
      <div className="input-area">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="やることを入力"
        />
        <button onClick={addTodo}>追加</button>
      </div>
      <ul className="todo-list">
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

TypeScript版(App.tsx):

TSX
import { useState, type JSX } from 'react'
import './App.css'

function App(): JSX.Element {
  const [todos, setTodos] = useState<string[]>([])
  const [inputValue, setInputValue] = useState<string>('')

  const addTodo = (): void => {
    if (inputValue.trim()) {
      setTodos([...todos, inputValue])
      setInputValue('')
    }
  }

  return (
    <div className="app">
      <h1>シンプルTodo</h1>
      <div className="input-area">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="やることを入力"
        />
        <button onClick={addTodo}>追加</button>
      </div>
      <ul className="todo-list">
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

まとめ

この記事では、Reactプロジェクトの構成を詳しく学びました。

学んだこと:

  • プロジェクトのディレクトリ構造
  • 各ファイルの役割(main.jsx、App.jsx、index.htmlなど)
  • JavaScriptとTypeScriptの違い
  • publicとassetsの使い分け
  • HMR(ホットモジュールリプレースメント)
  • ビルドと本番環境への準備

重要なポイント:

  • main.jsxがエントリーポイント
  • App.jsxがメインコンポーネント
  • index.htmlのrootにReactアプリがマウントされる
  • HMRで効率的に開発できる

次のステップ: 次回は、JSXの詳細な書き方とルールについて深く学びます。条件分岐、リスト表示、イベント処理など、実践的なJSXの使い方をマスターしましょう!

プロジェクト構成を理解することで、ファイルを探したり編集したりする作業がスムーズになります。最初は覚えることが多いですが、何度も触っているうちに自然と身につきますよ!