React基礎①

Reactとは、JSX、コンポーネント、Propsの使い方

Reactとは?

ユーザインターフェース構築のためのJavaScriptライブラリ

  • Meta(旧Facebook)が2013年にオープンソース化
  • コンポーネントベースのアーキテクチャ
  • 仮想DOMによる効率的な更新
  • 宣言的なプログラミングモデル
「何を表示するか」を記述すれば、「どうやって更新するか」はReactが処理する

従来のDOM操作の問題点

手動DOM操作の課題

  • 状態とUIの同期を手動で管理する必要
  • DOMの直接操作はバグの温床
  • アプリが大きくなると管理が困難
  • コードが複雑になりがち

DOM【Document Object Model】とは?

ブラウザがHTMLを解釈して作る、JavaScriptで操作可能なオブジェクト

DOM

JavaScript(Reactなし)

// 要素の取得と内容変更
document.getElementById('count').textContent = '1';

// CSSクラスの追加
document.querySelector('div').classList.add('highlighted');

// 新しい要素の作成と追加
const newButton = document.createElement('button');
newButton.textContent = 'クリック';
document.body.appendChild(newButton);

// イベントリスナーの追加
newButton.addEventListener('click', handleClick);
問題:アプリが大きくなると手動更新が地獄になる

コード比較:DOM操作 vs React

従来のDOM操作

const button = document.getElementById('btn');
const counter = document.getElementById('count');
let count = 0;

button.addEventListener('click', () => {
  count++;
  counter.textContent = count;
  
  // 他の要素も手動で更新
  updateHeader(count);
  updateFooter(count);
});

React

<div>
  <h1>カウンター</h1>
  <span>{count}</span>
  <button onClick={handleClick}>
    +1
  </button>
</div>
Reactなら「何を表示するか」だけ書けばOK

Reactのアプローチ

宣言的UI

「状態が変わったら全体を再描画」という発想の転換

  • 状態管理の単純化:状態を変更するだけでUIが自動更新
  • 仮想DOM:メモリ上で差分を計算し、必要な部分だけ更新
  • コンポーネント思考:UIを再利用可能な部品として構築
従来の「どうやって更新するか」から「何を表示するか」への思考転換

仮想DOM【Virtual DOM】とは?

メモリ上にDOMの軽量コピーを保持する仕組み

  • 実際のDOMより高速に操作可能
  • 変更前後を比較して差分を検出
  • 最小限の実DOM更新のみ実行
「全体を再描画」と言いつつ、実際は必要な部分だけ更新

仮想DOMの動作プロセス

Virtual DOM Process

1. 状態変更

状態またはpropsの変更

2. 新しい仮想DOMツリー作成

新しい状態に基づいてメモリ上に仮想DOMを構築

3. 差分検出【Diffing】

前回の仮想DOMと今回の仮想DOMを比較

4. 最小限更新【Reconciliation】

変更された部分のみ実DOMに反映

なぜ仮想DOMは高速なのか?

実DOM操作

  • ブラウザのレンダリングエンジンが重い
  • レイアウト計算が発生
  • 再描画処理が必要
  • 1つの変更でも全体に影響

仮想DOM

  • JavaScriptオブジェクトなので軽量
  • メモリ上での比較計算
  • バッチ処理で最適化
  • 変更部分のみ実DOM更新

仮想DOM 具体例

JSX

<div>
  <h1>カウント: {count}</h1>
  <button>+1</button>
</div>

仮想DOM(概念)

{
  type: 'div',
  children: [
    {
      type: 'h1',
      children: `カウント: ${count}`
    },
    {
      type: 'button',
      children: '+1'
    }
  ]
}
countが0→1に変わった時、h1のテキスト部分のみ実DOM更新

なぜReactを学ぶのか?

  • 手動DOM操作の複雑さを解決
  • 状態とUIの同期を自動化
  • 再利用可能なコンポーネント設計
  • 現代的なWeb開発の標準

JSX = JavaScript + HTML風の文法

普通のHTMLから始めよう

HTMLをそのまま書ける

<div class="greeting">
  <h1>Hello, World!</h1>
  <p>今日はいい天気ですね</p>
</div>

→ JSXならこれをJS関数の中に書ける

JSXの実際

function Greeting() {
  return (
    <div className="greeting">
      <h1>Hello, World!</h1>
      <p>今日はいい天気ですね</p>
    </div>
  );
}
classNameに注意! classはJSの予約語だから

JSXの正体

JSXはこれにコンパイルされる:

// JSX
<div className="greeting">Hello!</div>

// ↓ コンパイル後
React.createElement(
  "div",
  { className: "greeting" },
  "Hello!"
);
つまりJSX = syntactic sugar

JSXのルール

1. 必ず1つの要素を返す

// ❌ ダメ
return (
  <h1>Title</h1>
  <p>Content</p>
);

// ✅ OK - Fragment使用
return (
  <>
    <h1>Title</h1>
    <p>Content</p>
  </>
);

2. すべてのタグを閉じる

// ❌ HTML風だとダメ
<br>
<img src="photo.jpg">
<input type="text">

// ✅ 必ず閉じる
<br />
<img src="photo.jpg" />
<input type="text" />

3. camelCase属性

// ❌ HTML属性名
<div class="container" onclick="handleClick()">
<label for="email">

// ✅ camelCase
<div className="container" onClick={handleClick}>
<label htmlFor="email">
classclassName
forhtmlFor
onclickonClick

理解度チェック①【JSXルール】

どれが正しいJSX?

{ /* A */ }
return (
    <div>
        <h1>Title</h1>
        <p>Text</p>
    </div>
);

{ /* B */ }
return (
    <h1>Title</h1>
    <p>Text</p>
);
{ /* C */ }
return (
    <>
        <h1>Title</h1>
        <p>Text</p>
    </>
);
A) <div><h1>Title</h1><p>Text</p></div>
B) <h1>Title</h1><p>Text</p>
C) <><h1>Title</h1><p>Text</p></>
D) AとC両方
答え: D) 単一の親要素またはFragment必須

JSXにJavaScriptを埋め込む

{}で動的コンテンツ

変数を表示: {}

function User({ name, email }) {
  const greeting = 'こんにちは';
  
  return (
    <div>
      <h2>{greeting}, {name}さん</h2>
      <p>{email}</p>
      <p>今年は{new Date().getFullYear()}年です。</p>
    </div>
  );
}
{}の中は普通のJavaScript式

条件レンダリング: && 演算子

function UserProfile({ user }) {
  return (
    <div>
      <h1>Welcome {user.name}!</h1>
      {user.isAdmin && (
        <div className="admin-panel">  {/* trueの時だけ表示 */}
          Admin tools
        </div>
      )}
      {user.notifications.length > 0 && <span>You have {user.notifications.length} messages</span>}
    </div>
  );
}
condition && JSX
falseの時は何も表示しない(elseが不要な時)

条件レンダリング: 三項演算子

function UserProfile({ user, isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>Welcome {user.name}!</h1>  {/* trueの場合 */}
      ) : (
        <h1>Please log in</h1>         {/* falseの場合 */}
      )}
    </div>
  );
}
condition ? trueの時 : falseの時
必ず両方の場合を書く必要がある

ERBとの比較

JSX:

{user.loggedIn ? (
  <h1>Welcome {user.name}!</h1>
) : (
  <h1>Please log in</h1>
)}

{user.isAdmin && (
  <div className="admin-panel">
    Admin tools
  </div>
)}

ERB:

<% if user.logged_in? %>
  <h1>Welcome <%= user.name %>!</h1>
<% else %>
  <h1>Please log in</h1>
<% end %>

<% if user.admin? %>
  <div class="admin-panel">
    Admin tools
  </div>
<% end %>
JSXは式だからreturnの中に直接書ける

配列をレンダリング

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

JSX

<TodoList todos={[
        { id: 1, text: "起きます" },
        { id: 2, text: "コーヒーを飲みます" },
    ]} />

出力されるHTML

                            <ul>
    <li>起きます</li>
    <li>コーヒーを飲みます</li>
</ul>
                        
keyは必須!Reactが効率的に更新するため

key属性の重要性

リストをレンダリングする時の必須属性

❌ keyなし

{/* keyがないとリストの順序が変わった時に状態がズレる */}
{todos.map(todo => (
  <li>{todo.text}</li>  
))}

✅ keyあり

{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}
良いkey: item.id, user.uuid など一意で不変
悪いkey: index, Math.random() など

理解度チェック②【条件レンダリング】

何が表示される?

function AdminPanel() {
    const user = { name: "Ichigo", isAdmin: false };

    return <>{user.isAdmin && <div>Admin Panel</div>}</>;
}
A) <div>Admin Panel</div>
B) 何も表示されない
C) false
D) エラー
答え: B) &&の左側がfalseなので右側は評価されない

コンポーネント = 関数

再利用できるUI部品

なぜコンポーネント?

  • 再利用性:一度作れば何度でも使える
  • 保守性:修正は1箇所だけでOK
  • 可読性:コードが整理される
  • テスト:部品ごとに検証可能
// 100回書く代わりに
<UserCard user={user1} />
<UserCard user={user2} />
<UserCard user={user3} />
レゴブロックみたいにUIを組み立てる

最初のコンポーネント

// これがコンポーネント
function Welcome() {
  return <h1>Hello, World!</h1>;
}

// 使う時
function App() {
  return (
    <div>
      <Welcome />
      <Welcome />
    </div>
  );
}
関数名は大文字で始める(HTMLタグと区別するため)

コンポーネントの解剖学

// 1. 関数宣言(大文字で始める)
function UserCard() {
  // 2. ロジック(普通のJS)
  const userName = 'Kurosaki Ichigo';
  
  // 3. JSXを返す(必須)
  return (
    <div className="user-card">
      <h3>{userName}</h3>
    </div>
  );
}

コンポーネント合成

// 小さいコンポーネント
function Header() { return <h1>ジョジョファンサイト</h1>; }

function Navigation() {
  return (
    <nav>
      <a href="#home">ホーム</a>
      <a href="#stands">スタンド</a>
    </nav>
  );
}

function Footer() { return <p>© 2025 ジョジョファンサイト</p>; }
// 合成して大きいコンポーネント  
function App() {
  return (
    <div>
      <Header />
      <Navigation />
      <main>メインコンテンツ</main>
      <Footer />
    </div>
  );
}

Props = 関数の引数

コンポーネントにデータを渡す

Props基本

// 普通の関数
function greet(name, age) {
  return `こんにちは、${name}さん(${age}歳)`;
}

// Reactコンポーネント
function Greeting({ name, age }) {
  return <p>こんにちは、{name}さん({age}歳)</p>;
}

// 使用
<Greeting name="Kurosaki Ichigo" age={15} />

propsは読み取り専用

Props受け取り方法

方法1: propsオブジェクト


function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

方法2: 分割代入【destructuring】


function Greeting({ name, age }) {
  return <h1>Hello, {name}! You are {age}</h1>;
}

方法3: デフォルト値


function Greeting({ name = "Guest", age = 0 }) {
  return <h1>Hello, {name}! You are {age}</h1>;
}
分割代入はオブジェクトのプロパティを直接変数に展開する

children prop

// Cardコンポーネント
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}
// 使用方法
<Card title="Profile">
  <p>This goes into children</p>
  <button>Edit Profile</button>
</Card>
children = タグで囲んだ中身すべて

イベントハンドラー

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}
// 使用
function App() {
  const handleClick = () => alert('クリック!');
  
  return (
    <Button onClick={handleClick}>
      クリックして
    </Button>
  );
}

Props vs Rails partials

Reactのcomponent:

<UserCard 
  name={user.name}
  email={user.email} 
  isAdmin={true} />

Railsのpartial:

<%= render 'user_card', 
    user: @user, 
    show_admin: true %>
props = partial variables みたいなもの

理解度チェック③【Props】

childrenには何が入る?

function Card({ title, children }) {
    return (
        <div>
            <h2>{title}</h2>
            {children}
        </div>
    );
}

<Card title="Profile">
    <p>Content here</p>
</Card>
A) Content here
B) <div><h2>{title}</h2>Content here</div>
C) <p>Content here</p>
D) <h2>Profile</h2>Content here
答え: C) childrenはコンポーネントの開始・終了タグの間の要素

おまけ: デバッグのコツ

バグと戦うための武器

よくあるミス

コンポーネント名が小文字

function welcome() {  // 小文字ダメ
  return <h1>Hello</h1>;
}

<welcome />  // HTMLタグと判断される

key属性忘れ

{users.map(user => 
  <div>{user.name}</div>  // keyがない
)}

class属性

<div class="container">  // classNameが正しい

return忘れ

function Header() {
  <h1>Title</h1>;  // returnがない
}

武器1: React Developer Tools

ブラウザ拡張機能 Reactの状態を可視化

  • Chrome/Firefox/Edge対応
  • コンポーネントツリー表示
  • Props/State監視
  • パフォーマンス分析
開発者ツールにReactタブが追加される

武器2: console.log in JSX

function TodoItem({ todo }) {
  console.log('TodoItem rendered:', todo);  // 基本のログ
  
  return (
    <li>
      {console.log('Rendering todo text:', todo.text) || null}
      {todo.text}
    </li>
  );
}
JSX内でconsole.logする場合は|| nullで戻り値を無効化

デバッグフロー

  1. エラーメッセージを読む
  2. console.logで値を確認
  3. React DevToolsでprops確認
  4. 最小限のコードで再現

React 実践演習

1つの課題 (15-20分)

今日学んだ内容を実際に使ってみよう!

セットアップ手順

  1. masterブランチに切り替え、更新を取得する
    git switch master
    git pull origin main
  2. パッケージをインストールする
    cd react-lessons/
    npm install
  3. 開発サーバーを起動する
    npm run dev
  4. ブラウザで「http://localhost:5173/」を開く

課題1 Props付きのコンポーネント

<ZanpakutoCard> コンポーネントを作成してください

  • zanpakuto オブジェクトを Props として受け取る
  • 以下のプロパティをすべて表示すること:
    • name, owner, type, bankai, shikaiAbility, powerLevel, isReleased
  • bankai は存在する場合のみ表示
export interface Zanpakuto {
  name: string;
  owner: string;
  type: "melee" | "kido" | "elemental" | "illusion";
  bankai?: string;
  shikaiAbility: string;
  powerLevel: number;
  isReleased: boolean;
}
    

課題1 解答例

props を受け取って全てのプロパティを表示する

function ZanpakutoCard({ zanpakuto }: { zanpakuto: Zanpakuto }) {
  const { name, owner, type, bankai, shikaiAbility, powerLevel, isReleased } = zanpakuto;  // 分割代入でプロパティを展開
  
  return (
    <div>
      <h2>{name}({owner}の斬魄刀)</h2> {/* JSXでは{}内にJavaScript式を書ける */}
      <p>タイプ: {type}</p>
      {bankai && <p>卍解: {bankai}</p>} {/* 条件付きレンダリングの&&演算子:左側がtruthyの場合、右側を実行 */}
      <p>始解能力: {shikaiAbility}</p>
      <p>霊圧レベル: {powerLevel}</p>
      <p>解放済み: {isReleased ? "はい" : "いいえ"}</p> {/* 三項演算子 - 条件 ? 真の場合 : 偽の場合 */}
    </div>
  );
}

第1回まとめ

今日学んだこと

  • Reactとは UIライブラリ、仮想DOM、宣言的プログラミング
  • JSX JavaScript + XML、{}で動的コンテンツ
  • コンポーネント 再利用可能なUI部品(関数)
  • Props コンポーネント間のデータ受け渡し
  • 条件レンダリング 三項演算子、&&演算子

次回予告

React②: useState、イベント処理、フォーム