React CDNで始める|インストール不要の超簡単スタート方法

React CDNを使ってインストール不要でReactを始める方法を詳しく解説。HTMLファイル1つで即座にReact開発をスタートできます。

Learning Next 運営
87 分で読めます

みなさん、Reactを始めたいと思ったことはありませんか?

「環境構築が面倒そう」 「Node.jsとかよくわからない」 「とりあえず触ってみたい」

そんな風に思ったことはありませんか?

実は、HTMLファイル1つでReactを始めることができるんです。 この記事では、CDNを使った超簡単なReactの始め方を詳しく解説します。

複雑な設定は一切不要! 今すぐにReactの世界に飛び込んでいきましょう。

CDNって何?なぜReactが簡単に始められるの?

まず、CDNの基本的な考え方を理解しましょう。

CDNの基本概念

CDNは、Content Delivery Networkの略です。 簡単に言うと、「インターネット上からライブラリを借りてくる」という感じです。

通常のReact開発では、こんな作業が必要です。

  • Node.jsのインストール
  • npmのセットアップ
  • create-react-appの実行
  • 複雑なフォルダ構成の理解

でも、CDNを使えば、これらの作業が一切不要になります。

CDNの特徴

CDNには、こんな特徴があります。

  • インストール不要: 何もダウンロードしなくてOK
  • 即座に開始: HTMLファイルを作るだけで始められる
  • 軽量: 必要最小限のファイルだけを使用
  • 学習に最適: 複雑な設定なしでReactの基本を理解

「難しそう...」と思うかもしれませんが、実は数分で完了します。

CDNが適している場面

CDNは、こんな場面で特に有効です。

  • Reactの学習: 基本概念を理解したい初心者
  • プロトタイプ作成: アイデアを素早く形にしたい
  • 既存サイトの改善: 一部分だけReactを使いたい
  • 教育現場: 授業やワークショップでの活用

心配いりません! 一つずつ丁寧に説明していきます。

最初のHello World

では、実際にReactアプリを作ってみましょう。

基本的なHTMLファイルの作成

まず、この内容でHTMLファイルを作成してください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React CDN サンプル</title>
</head>
<body>
    <div id="root"></div>

    <!-- React CDN -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <!-- Babel CDN -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <!-- Reactアプリケーション -->
    <script type="text/babel">
        function App() {
            return (
                <div>
                    <h1>Hello, React!</h1>
                    <p>CDNを使ってReactを始めました!</p>
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<App />);
    </script>
</body>
</html>

このファイルをブラウザで開いてみてください。 「Hello, React!」と表示されるはずです。

すごいですよね! たったこれだけで、Reactアプリが動いています。

コードの詳細解説

では、各部分が何をしているか見てみましょう。

1. React本体の読み込み

<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>

これは、React本体を読み込んでいます。 crossorigin属性は、セキュリティのために必要です。

2. ReactDOMの読み込み

<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

ReactDOMは、ReactコンポーネントをブラウザのDOMに表示するためのライブラリです。 Reactだけでは、ブラウザに表示できないんです。

3. Babelの読み込み

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

Babelは、JSXをJavaScriptに変換してくれるツールです。 ブラウザはJSXを理解できないので、Babelが必要になります。

4. Reactアプリケーション

<script type="text/babel">
    function App() {
        return (
            <div>
                <h1>Hello, React!</h1>
                <p>CDNを使ってReactを始めました!</p>
            </div>
        );
    }

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
</script>

type="text/babel"に注目してください。 これにより、Babelがこのスクリプトを処理してくれます。

App関数は、Reactコンポーネントです。 HTMLっぽい書き方(JSX)でUIを定義しています。

最後の2行で、作成したコンポーネントをブラウザに表示しています。

実際に動かしてみよう

このHTMLファイルをブラウザで開くと、こんな感じになります。

  1. ブラウザがHTMLを読み込む
  2. Reactライブラリが読み込まれる
  3. BabelがJSXをJavaScriptに変換
  4. Reactコンポーネントが表示される

「本当にこれだけで動くの?」と思うかもしれませんが、実際に試してみてください。

きっと驚くと思います!

実用的なカウンターアプリを作ってみよう

Hello Worldができたので、次はもう少し実用的なアプリを作ってみましょう。

インタラクティブなカウンター

ボタンを押すと数字が変わるカウンターアプリを作ってみます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reactカウンター</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .counter {
            text-align: center;
            padding: 20px;
            border: 2px solid #007bff;
            border-radius: 10px;
            margin: 20px 0;
        }
        .count {
            font-size: 48px;
            font-weight: bold;
            color: #007bff;
            margin: 20px 0;
        }
        button {
            font-size: 18px;
            padding: 10px 20px;
            margin: 0 10px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        .increment {
            background-color: #28a745;
            color: white;
        }
        .decrement {
            background-color: #dc3545;
            color: white;
        }
        .reset {
            background-color: #6c757d;
            color: white;
        }
    </style>
</head>
<body>
    <div id="root"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        function Counter() {
            const [count, setCount] = React.useState(0);

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

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

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

            return (
                <div className="counter">
                    <h1>Reactカウンター</h1>
                    <div className="count">{count}</div>
                    <div>
                        <button className="increment" onClick={increment}>
                            +1
                        </button>
                        <button className="decrement" onClick={decrement}>
                            -1
                        </button>
                        <button className="reset" onClick={reset}>
                            リセット
                        </button>
                    </div>
                </div>
            );
        }

        function App() {
            return (
                <div>
                    <h1>React CDN サンプルアプリ</h1>
                    <Counter />
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<App />);
    </script>
</body>
</html>

このコードを実行すると、数字が表示されて、ボタンを押すと数字が変わります。

すごいですよね! たったこれだけで、動的なWebアプリが作れました。

カウンターアプリの詳細解説

では、新しく出てきた部分を詳しく見てみましょう。

useState Hook

const [count, setCount] = React.useState(0);

これは、useState Hookと呼ばれるReactの機能です。

countは現在の数値を保持しています。 setCountは数値を変更するための関数です。 0は初期値です。

イベントハンドラー

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

これは、イベントハンドラーと呼ばれる関数です。 ボタンがクリックされたときに実行されます。

setCount(count + 1)で、現在の数値に1を足しています。

JSXでのイベント処理

<button className="increment" onClick={increment}>
    +1
</button>

onClick={increment}で、ボタンがクリックされたときにincrement関数を実行します。

HTMLのonclickとは少し書き方が違いますが、やっていることは同じです。

実際に動かしてみよう

このファイルをブラウザで開いて、ボタンをクリックしてみてください。

  • +1ボタン: 数字が1増える
  • -1ボタン: 数字が1減る
  • リセットボタン: 数字が0になる

「こんなに簡単にできるの?」と思うかもしれませんが、本当にこれだけです。

Reactの力を実感できたでしょうか?

もう少し複雑なToDoリストを作ってみよう

カウンターの次は、ToDoリストを作ってみましょう。

基本的なToDoリスト

タスクを追加・削除・完了マークできるToDoリストを作ります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React ToDoリスト</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .todo-app {
            background: white;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .todo-input {
            display: flex;
            margin-bottom: 20px;
        }
        .todo-input input {
            flex: 1;
            padding: 10px;
            font-size: 16px;
            border: 2px solid #ddd;
            border-radius: 5px 0 0 5px;
            border-right: none;
        }
        .todo-input button {
            padding: 10px 20px;
            font-size: 16px;
            background-color: #007bff;
            color: white;
            border: 2px solid #007bff;
            border-radius: 0 5px 5px 0;
            cursor: pointer;
        }
        .todo-item {
            display: flex;
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid #eee;
        }
        .todo-item.completed {
            opacity: 0.6;
            text-decoration: line-through;
        }
        .todo-item input[type="checkbox"] {
            margin-right: 10px;
        }
        .todo-text {
            flex: 1;
        }
        .delete-btn {
            background-color: #dc3545;
            color: white;
            border: none;
            padding: 5px 10px;
            border-radius: 3px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="root"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        function TodoApp() {
            const [todos, setTodos] = React.useState([]);
            const [inputValue, setInputValue] = React.useState('');

            const addTodo = () => {
                if (inputValue.trim() !== '') {
                    const newTodo = {
                        id: Date.now(),
                        text: inputValue.trim(),
                        completed: false
                    };
                    setTodos([...todos, newTodo]);
                    setInputValue('');
                }
            };

            const deleteTodo = (id) => {
                setTodos(todos.filter(todo => todo.id !== id));
            };

            const toggleTodo = (id) => {
                setTodos(todos.map(todo => 
                    todo.id === id 
                        ? { ...todo, completed: !todo.completed }
                        : todo
                ));
            };

            const handleKeyPress = (e) => {
                if (e.key === 'Enter') {
                    addTodo();
                }
            };

            return (
                <div className="todo-app">
                    <h1>React ToDoリスト</h1>
                    
                    <div className="todo-input">
                        <input
                            type="text"
                            value={inputValue}
                            onChange={(e) => setInputValue(e.target.value)}
                            onKeyPress={handleKeyPress}
                            placeholder="新しいタスクを入力..."
                        />
                        <button onClick={addTodo}>追加</button>
                    </div>

                    <div className="todo-list">
                        {todos.length === 0 ? (
                            <p>タスクがありません。新しいタスクを追加してください。</p>
                        ) : (
                            todos.map(todo => (
                                <div
                                    key={todo.id}
                                    className={`todo-item ${todo.completed ? 'completed' : ''}`}
                                >
                                    <input
                                        type="checkbox"
                                        checked={todo.completed}
                                        onChange={() => toggleTodo(todo.id)}
                                    />
                                    <span className="todo-text">{todo.text}</span>
                                    <button
                                        className="delete-btn"
                                        onClick={() => deleteTodo(todo.id)}
                                    >
                                        削除
                                    </button>
                                </div>
                            ))
                        )}
                    </div>

                    <div style={{ marginTop: '20px', color: '#666' }}>
                        <p>完了: {todos.filter(todo => todo.completed).length} / 全体: {todos.length}</p>
                    </div>
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<TodoApp />);
    </script>
</body>
</html>

このコードを実行すると、本格的なToDoリストが完成します。

ちょっと長いですが、大丈夫です! 一つずつ見ていきましょう。

ToDoリストの詳細解説

新しく出てきた概念を詳しく解説します。

配列の状態管理

const [todos, setTodos] = React.useState([]);

todosは、タスクの配列を保持しています。 最初は空の配列[]から始まります。

タスクの追加

const addTodo = () => {
    if (inputValue.trim() !== '') {
        const newTodo = {
            id: Date.now(),
            text: inputValue.trim(),
            completed: false
        };
        setTodos([...todos, newTodo]);
        setInputValue('');
    }
};

newTodoというオブジェクトを作成しています。 Date.now()で一意のIDを生成しています。

[...todos, newTodo]で、既存の配列に新しいタスクを追加しています。 これをスプレッド演算子と呼びます。

タスクの削除

const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
};

filterメソッドで、指定されたID以外のタスクだけを残しています。

タスクの完了・未完了の切り替え

const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
        todo.id === id 
            ? { ...todo, completed: !todo.completed }
            : todo
    ));
};

mapメソッドで、各タスクを確認しています。 指定されたIDのタスクだけ、完了状態を反転させています。

リストの表示

todos.map(todo => (
    <div key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
        <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
        />
        <span className="todo-text">{todo.text}</span>
        <button
            className="delete-btn"
            onClick={() => deleteTodo(todo.id)}
        >
            削除
        </button>
    </div>
))

mapメソッドで、各タスクをHTMLに変換しています。 key={todo.id}は、Reactが効率的に更新するために必要です。

実際に使ってみよう

このToDoリストを実際に使ってみてください。

  1. テキストボックスにタスクを入力
  2. 「追加」ボタンをクリック(またはEnterキー)
  3. チェックボックスで完了・未完了を切り替え
  4. 「削除」ボタンでタスクを削除

「こんなに本格的なアプリが作れるの?」と思うかもしれませんが、これもCDNで簡単に実現できます。

すごいですよね!

コンポーネントを分割してみよう

ToDoリストが完成したので、次はコンポーネント分割を学びましょう。

なぜコンポーネントを分割するの?

大きなコンポーネントを小さく分割することで、以下のメリットがあります。

  • 理解しやすい: 各部分の役割が明確になる
  • 再利用できる: 同じコンポーネントを別の場所で使える
  • 保守しやすい: 修正するときに影響範囲が限定される

分割されたToDoリスト

同じToDoリストを、複数のコンポーネントに分割してみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React コンポーネント分割</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .todo-app {
            background: white;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .todo-input {
            display: flex;
            margin-bottom: 20px;
        }
        .todo-input input {
            flex: 1;
            padding: 10px;
            font-size: 16px;
            border: 2px solid #ddd;
            border-radius: 5px 0 0 5px;
            border-right: none;
        }
        .todo-input button {
            padding: 10px 20px;
            font-size: 16px;
            background-color: #007bff;
            color: white;
            border: 2px solid #007bff;
            border-radius: 0 5px 5px 0;
            cursor: pointer;
        }
        .todo-item {
            display: flex;
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid #eee;
        }
        .todo-item.completed {
            opacity: 0.6;
            text-decoration: line-through;
        }
        .todo-item input[type="checkbox"] {
            margin-right: 10px;
        }
        .todo-text {
            flex: 1;
        }
        .delete-btn {
            background-color: #dc3545;
            color: white;
            border: none;
            padding: 5px 10px;
            border-radius: 3px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="root"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        // 1. ToDoアイテムコンポーネント
        function TodoItem({ todo, onToggle, onDelete }) {
            return (
                <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
                    <input
                        type="checkbox"
                        checked={todo.completed}
                        onChange={() => onToggle(todo.id)}
                    />
                    <span className="todo-text">{todo.text}</span>
                    <button
                        className="delete-btn"
                        onClick={() => onDelete(todo.id)}
                    >
                        削除
                    </button>
                </div>
            );
        }

        // 2. ToDoリストコンポーネント
        function TodoList({ todos, onToggle, onDelete }) {
            if (todos.length === 0) {
                return <p>タスクがありません。新しいタスクを追加してください。</p>;
            }

            return (
                <div className="todo-list">
                    {todos.map(todo => (
                        <TodoItem
                            key={todo.id}
                            todo={todo}
                            onToggle={onToggle}
                            onDelete={onDelete}
                        />
                    ))}
                </div>
            );
        }

        // 3. ToDoインプットコンポーネント
        function TodoInput({ value, onChange, onAdd }) {
            const handleKeyPress = (e) => {
                if (e.key === 'Enter') {
                    onAdd();
                }
            };

            return (
                <div className="todo-input">
                    <input
                        type="text"
                        value={value}
                        onChange={(e) => onChange(e.target.value)}
                        onKeyPress={handleKeyPress}
                        placeholder="新しいタスクを入力..."
                    />
                    <button onClick={onAdd}>追加</button>
                </div>
            );
        }

        // 4. 統計表示コンポーネント
        function TodoStats({ todos }) {
            const completedCount = todos.filter(todo => todo.completed).length;
            const totalCount = todos.length;

            return (
                <div style={{ marginTop: '20px', color: '#666' }}>
                    <p>完了: {completedCount} / 全体: {totalCount}</p>
                    {totalCount > 0 && (
                        <p>進捗: {Math.round((completedCount / totalCount) * 100)}%</p>
                    )}
                </div>
            );
        }

        // 5. メインアプリケーションコンポーネント
        function TodoApp() {
            const [todos, setTodos] = React.useState([]);
            const [inputValue, setInputValue] = React.useState('');

            const addTodo = () => {
                if (inputValue.trim() !== '') {
                    const newTodo = {
                        id: Date.now(),
                        text: inputValue.trim(),
                        completed: false
                    };
                    setTodos([...todos, newTodo]);
                    setInputValue('');
                }
            };

            const deleteTodo = (id) => {
                setTodos(todos.filter(todo => todo.id !== id));
            };

            const toggleTodo = (id) => {
                setTodos(todos.map(todo => 
                    todo.id === id 
                        ? { ...todo, completed: !todo.completed }
                        : todo
                ));
            };

            return (
                <div className="todo-app">
                    <h1>React ToDoリスト(コンポーネント分割版)</h1>
                    
                    <TodoInput
                        value={inputValue}
                        onChange={setInputValue}
                        onAdd={addTodo}
                    />

                    <TodoList
                        todos={todos}
                        onToggle={toggleTodo}
                        onDelete={deleteTodo}
                    />

                    <TodoStats todos={todos} />
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<TodoApp />);
    </script>
</body>
</html>

機能は同じですが、コードがとても見やすくなりました。

分割されたコンポーネントの解説

各コンポーネントの役割を見てみましょう。

1. TodoItem コンポーネント

function TodoItem({ todo, onToggle, onDelete }) {
    return (
        <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
            <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => onToggle(todo.id)}
            />
            <span className="todo-text">{todo.text}</span>
            <button
                className="delete-btn"
                onClick={() => onDelete(todo.id)}
            >
                削除
            </button>
        </div>
    );
}

1つのタスクを表示するコンポーネントです。 { todo, onToggle, onDelete }で、親から情報を受け取っています。

2. TodoList コンポーネント

function TodoList({ todos, onToggle, onDelete }) {
    if (todos.length === 0) {
        return <p>タスクがありません。新しいタスクを追加してください。</p>;
    }

    return (
        <div className="todo-list">
            {todos.map(todo => (
                <TodoItem
                    key={todo.id}
                    todo={todo}
                    onToggle={onToggle}
                    onDelete={onDelete}
                />
            ))}
        </div>
    );
}

タスクの一覧を表示するコンポーネントです。 TodoItemを複数使って、リストを構成しています。

3. TodoInput コンポーネント

function TodoInput({ value, onChange, onAdd }) {
    const handleKeyPress = (e) => {
        if (e.key === 'Enter') {
            onAdd();
        }
    };

    return (
        <div className="todo-input">
            <input
                type="text"
                value={value}
                onChange={(e) => onChange(e.target.value)}
                onKeyPress={handleKeyPress}
                placeholder="新しいタスクを入力..."
            />
            <button onClick={onAdd}>追加</button>
        </div>
    );
}

新しいタスクを入力するコンポーネントです。 Enterキーでもタスクを追加できるようになっています。

4. TodoStats コンポーネント

function TodoStats({ todos }) {
    const completedCount = todos.filter(todo => todo.completed).length;
    const totalCount = todos.length;

    return (
        <div style={{ marginTop: '20px', color: '#666' }}>
            <p>完了: {completedCount} / 全体: {totalCount}</p>
            {totalCount > 0 && (
                <p>進捗: {Math.round((completedCount / totalCount) * 100)}%</p>
            )}
        </div>
    );
}

統計情報を表示するコンポーネントです。 完了率の計算も行っています。

5. メインアプリケーション

function TodoApp() {
    const [todos, setTodos] = React.useState([]);
    const [inputValue, setInputValue] = React.useState('');

    // ... 各種関数

    return (
        <div className="todo-app">
            <h1>React ToDoリスト(コンポーネント分割版)</h1>
            
            <TodoInput
                value={inputValue}
                onChange={setInputValue}
                onAdd={addTodo}
            />

            <TodoList
                todos={todos}
                onToggle={toggleTodo}
                onDelete={deleteTodo}
            />

            <TodoStats todos={todos} />
        </div>
    );
}

全体をまとめるメインのコンポーネントです。 状態管理と、各コンポーネントの連携を行っています。

コンポーネント分割のメリット

この分割により、以下のメリットが得られます。

  • 理解しやすい: 各コンポーネントの役割が明確
  • 修正しやすい: 1つのコンポーネントだけを変更できる
  • テストしやすい: 個別にテストできる
  • 再利用できる: 他のプロジェクトでも使える

「最初は1つのコンポーネントで十分では?」と思うかもしれませんが、アプリが大きくなると分割の重要性がわかってきます。

実際に動かしてみよう

このコンポーネント分割版も、機能は全く同じです。

でも、コードが圧倒的に見やすくなりました。 これがReactの真の力なんです。

すごいですよね!

他のライブラリとの組み合わせ

CDNの素晴らしいところは、他のライブラリと簡単に組み合わせできることです。

Bootstrapと組み合わせてみよう

まず、見た目を美しくするBootstrapと組み合わせてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React + Bootstrap</title>
    <!-- Bootstrap CDN -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div id="root"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        function UserCard({ user }) {
            return (
                <div className="col-md-4 mb-3">
                    <div className="card h-100">
                        <img 
                            src={user.avatar} 
                            className="card-img-top" 
                            alt={user.name}
                            style={{ height: '200px', objectFit: 'cover' }}
                        />
                        <div className="card-body">
                            <h5 className="card-title">{user.name}</h5>
                            <p className="card-text">{user.email}</p>
                            <p className="card-text">
                                <small className="text-muted">{user.role}</small>
                            </p>
                        </div>
                        <div className="card-footer">
                            <button className="btn btn-primary btn-sm me-2">
                                プロフィール
                            </button>
                            <button className="btn btn-outline-secondary btn-sm">
                                メッセージ
                            </button>
                        </div>
                    </div>
                </div>
            );
        }

        function UserList() {
            const [users] = React.useState([
                {
                    id: 1,
                    name: '田中太郎',
                    email: 'tanaka@example.com',
                    role: '開発者',
                    avatar: 'https://via.placeholder.com/300x200/007bff/ffffff?text=田中太郎'
                },
                {
                    id: 2,
                    name: '佐藤花子',
                    email: 'sato@example.com',
                    role: 'デザイナー',
                    avatar: 'https://via.placeholder.com/300x200/28a745/ffffff?text=佐藤花子'
                },
                {
                    id: 3,
                    name: '山田次郎',
                    email: 'yamada@example.com',
                    role: 'マネージャー',
                    avatar: 'https://via.placeholder.com/300x200/dc3545/ffffff?text=山田次郎'
                }
            ]);

            return (
                <div className="container mt-5">
                    <div className="row">
                        <div className="col-12">
                            <h1 className="text-center mb-4">チームメンバー</h1>
                        </div>
                    </div>
                    <div className="row">
                        {users.map(user => (
                            <UserCard key={user.id} user={user} />
                        ))}
                    </div>
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<UserList />);
    </script>
</body>
</html>

このコードを実行すると、プロ級の見た目のカードリストが表示されます。

Bootstrap組み合わせの解説

Bootstrap CDNの読み込み

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">

BootstrapのCSSをCDNで読み込んでいます。 これだけで、美しいスタイルが使えるようになります。

Bootstrapクラスの使用

<div className="col-md-4 mb-3">
    <div className="card h-100">
        <img 
            src={user.avatar} 
            className="card-img-top" 
            alt={user.name}
            style={{ height: '200px', objectFit: 'cover' }}
        />
        <div className="card-body">
            <h5 className="card-title">{user.name}</h5>
            <p className="card-text">{user.email}</p>
        </div>
    </div>
</div>

col-md-4cardcard-bodyなどは、すべてBootstrapのクラスです。 これらを使うことで、プロフェッショナルな見た目になります。

Chart.jsと組み合わせてみよう

次は、グラフを表示するChart.jsと組み合わせてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React + Chart.js</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .chart-container {
            margin: 20px 0;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 20px;
        }
        .stat-card {
            padding: 20px;
            background-color: #f8f9fa;
            border-radius: 5px;
            text-align: center;
        }
        .stat-value {
            font-size: 24px;
            font-weight: bold;
            margin-bottom: 5px;
        }
    </style>
</head>
<body>
    <div id="root"></div>

    <!-- Chart.js CDN -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        function SalesChart() {
            const chartRef = React.useRef(null);
            const chartInstanceRef = React.useRef(null);

            React.useEffect(() => {
                const ctx = chartRef.current.getContext('2d');
                
                // 既存のチャートがあれば破棄
                if (chartInstanceRef.current) {
                    chartInstanceRef.current.destroy();
                }

                chartInstanceRef.current = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
                        datasets: [{
                            label: '売上',
                            data: [120, 190, 300, 500, 200, 300],
                            borderColor: 'rgb(75, 192, 192)',
                            backgroundColor: 'rgba(75, 192, 192, 0.2)',
                            tension: 0.1
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: '月別売上推移'
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: true
                            }
                        }
                    }
                });

                // クリーンアップ関数
                return () => {
                    if (chartInstanceRef.current) {
                        chartInstanceRef.current.destroy();
                    }
                };
            }, []);

            return (
                <div className="chart-container">
                    <canvas ref={chartRef}></canvas>
                </div>
            );
        }

        function Dashboard() {
            const [salesData] = React.useState({
                total: 1610,
                average: 268,
                growth: 15.2
            });

            return (
                <div>
                    <h1>売上ダッシュボード</h1>
                    
                    <div className="stats-grid">
                        <div className="stat-card">
                            <div className="stat-value" style={{ color: '#007bff' }}>
                                ¥{salesData.total.toLocaleString()}万
                            </div>
                            <div>総売上</div>
                        </div>
                        <div className="stat-card">
                            <div className="stat-value" style={{ color: '#28a745' }}>
                                ¥{salesData.average.toLocaleString()}万
                            </div>
                            <div>月平均</div>
                        </div>
                        <div className="stat-card">
                            <div className="stat-value" style={{ color: '#fd7e14' }}>
                                +{salesData.growth}%
                            </div>
                            <div>成長率</div>
                        </div>
                    </div>

                    <SalesChart />
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<Dashboard />);
    </script>
</body>
</html>

このコードを実行すると、本格的なダッシュボードが表示されます。

Chart.js組み合わせの解説

Chart.js CDNの読み込み

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

Chart.jsのライブラリをCDNで読み込んでいます。

useRefとuseEffectの使用

const chartRef = React.useRef(null);
const chartInstanceRef = React.useRef(null);

React.useEffect(() => {
    const ctx = chartRef.current.getContext('2d');
    
    // チャートの作成
    chartInstanceRef.current = new Chart(ctx, {
        type: 'line',
        data: {
            labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
            datasets: [{
                label: '売上',
                data: [120, 190, 300, 500, 200, 300],
                borderColor: 'rgb(75, 192, 192)',
                backgroundColor: 'rgba(75, 192, 192, 0.2)',
                tension: 0.1
            }]
        },
        options: {
            responsive: true,
            plugins: {
                title: {
                    display: true,
                    text: '月別売上推移'
                }
            }
        }
    });

    // クリーンアップ関数
    return () => {
        if (chartInstanceRef.current) {
            chartInstanceRef.current.destroy();
        }
    };
}, []);

useRefでDOM要素を参照しています。 useEffectでコンポーネントの初期化時にチャートを作成しています。

クリーンアップ関数で、不要になったチャートを削除しています。

他のライブラリとの組み合わせ

CDNを使えば、以下のようなライブラリも簡単に組み合わせできます。

  • Font Awesome: アイコンライブラリ
  • Lodash: ユーティリティライブラリ
  • Moment.js: 日付操作ライブラリ
  • Axios: HTTP通信ライブラリ

「こんなに簡単に組み合わせられるの?」と思うかもしれませんが、CDNなら本当に簡単です。

必要なものを<script>タグで読み込むだけなんです。

実際の活用場面

React CDNが実際にどのような場面で活用されるかを見てみましょう。

プロトタイプ作成

アイデアを素早く形にしたいときの例を見てみます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>レストラン予約システム プロトタイプ</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .form-container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #333;
        }
        input, select {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
        }
        button:hover {
            background-color: #0056b3;
        }
        .success-message {
            background-color: #d4edda;
            color: #155724;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
        .reservation-summary {
            background-color: #f8f9fa;
            padding: 20px;
            border-radius: 5px;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <div id="root"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        function ReservationForm() {
            const [reservation, setReservation] = React.useState({
                name: '',
                phone: '',
                date: '',
                time: '',
                guests: 2
            });
            const [submitted, setSubmitted] = React.useState(false);

            const handleChange = (field, value) => {
                setReservation(prev => ({
                    ...prev,
                    [field]: value
                }));
            };

            const handleSubmit = (e) => {
                e.preventDefault();
                setSubmitted(true);
                // 実際のシステムでは、ここでAPIにデータを送信
                console.log('予約データ:', reservation);
            };

            if (submitted) {
                return (
                    <div className="form-container">
                        <div className="success-message">
                            <h2>予約が完了しました!</h2>
                            <p>ご予約ありがとうございます。</p>
                        </div>
                        <div className="reservation-summary">
                            <h3>予約内容</h3>
                            <p><strong>お名前:</strong> {reservation.name}</p>
                            <p><strong>電話番号:</strong> {reservation.phone}</p>
                            <p><strong>予約日:</strong> {reservation.date}</p>
                            <p><strong>時間:</strong> {reservation.time}</p>
                            <p><strong>人数:</strong> {reservation.guests}名</p>
                        </div>
                        <button onClick={() => setSubmitted(false)}>
                            新しい予約を作成
                        </button>
                    </div>
                );
            }

            return (
                <div className="form-container">
                    <h1>レストラン予約システム</h1>
                    <p>以下のフォームに必要事項を入力してください。</p>
                    <form onSubmit={handleSubmit}>
                        <div className="form-group">
                            <label>お名前</label>
                            <input
                                type="text"
                                value={reservation.name}
                                onChange={(e) => handleChange('name', e.target.value)}
                                required
                            />
                        </div>

                        <div className="form-group">
                            <label>電話番号</label>
                            <input
                                type="tel"
                                value={reservation.phone}
                                onChange={(e) => handleChange('phone', e.target.value)}
                                required
                            />
                        </div>

                        <div className="form-group">
                            <label>予約日</label>
                            <input
                                type="date"
                                value={reservation.date}
                                onChange={(e) => handleChange('date', e.target.value)}
                                min={new Date().toISOString().split('T')[0]}
                                required
                            />
                        </div>

                        <div className="form-group">
                            <label>時間</label>
                            <select
                                value={reservation.time}
                                onChange={(e) => handleChange('time', e.target.value)}
                                required
                            >
                                <option value="">時間を選択</option>
                                <option value="17:00">17:00</option>
                                <option value="17:30">17:30</option>
                                <option value="18:00">18:00</option>
                                <option value="18:30">18:30</option>
                                <option value="19:00">19:00</option>
                                <option value="19:30">19:30</option>
                                <option value="20:00">20:00</option>
                                <option value="20:30">20:30</option>
                                <option value="21:00">21:00</option>
                            </select>
                        </div>

                        <div className="form-group">
                            <label>人数</label>
                            <select
                                value={reservation.guests}
                                onChange={(e) => handleChange('guests', parseInt(e.target.value))}
                            >
                                {[1,2,3,4,5,6,7,8,9,10].map(num => (
                                    <option key={num} value={num}>{num}名</option>
                                ))}
                            </select>
                        </div>

                        <button type="submit">予約する</button>
                    </form>
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<ReservationForm />);
    </script>
</body>
</html>

このプロトタイプでは、以下の機能を実装しています。

  • フォーム入力: 名前、電話番号、日付、時間、人数
  • バリデーション: 必須項目のチェック
  • 動的な処理: 予約完了後の表示切り替え
  • ユーザビリティ: 今日以降の日付のみ選択可能

既存サイトへの部分導入

既存のWebサイトに、一部分だけReactを導入する例です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>既存サイト + React部分導入</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            line-height: 1.6;
        }
        header {
            background-color: #333;
            color: white;
            padding: 1rem;
            text-align: center;
        }
        .content {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .react-widget {
            border: 2px solid #007bff;
            border-radius: 8px;
            padding: 20px;
            margin: 20px 0;
            background-color: #f8f9fa;
        }
        .react-widget h3 {
            color: #007bff;
            margin-top: 0;
        }
        footer {
            background-color: #333;
            color: white;
            text-align: center;
            padding: 1rem;
            margin-top: 40px;
        }
        .traditional-content {
            background-color: #ffffff;
            padding: 20px;
            margin: 20px 0;
            border-left: 4px solid #28a745;
        }
    </style>
</head>
<body>
    <!-- 既存のHTMLコンテンツ -->
    <header>
        <h1>株式会社サンプル</h1>
        <p>革新的なWebソリューションを提供</p>
    </header>

    <div class="content">
        <div class="traditional-content">
            <h2>会社概要</h2>
            <p>私たちは、最新のWeb技術を活用して、お客様のビジネスをサポートしています。</p>
            <p>従来のHTMLコンテンツと、最新のReactコンポーネントを組み合わせることで、効率的なWebサイト開発を実現しています。</p>
        </div>
        
        <!-- React部分のコンテナ -->
        <div class="react-widget">
            <div id="contact-form"></div>
        </div>

        <div class="traditional-content">
            <h2>サービス紹介</h2>
            <p>以下のサービスを提供しています:</p>
            <ul>
                <li>Webサイト制作</li>
                <li>システム開発</li>
                <li>保守・運用</li>
                <li>コンサルティング</li>
            </ul>
        </div>
        
        <!-- もう一つのReact部分 -->
        <div class="react-widget">
            <div id="newsletter-signup"></div>
        </div>

        <div class="traditional-content">
            <h2>お客様の声</h2>
            <p>「迅速で丁寧な対応をしていただき、期待以上の成果を得ることができました。」</p>
            <p>「技術力の高さと、分かりやすい説明で、安心してお任せできました。」</p>
        </div>
    </div>

    <footer>
        <p>&copy; 2024 株式会社サンプル. All rights reserved.</p>
    </footer>

    <!-- React CDN -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    
    <script type="text/babel">
        // お問い合わせフォームコンポーネント
        function ContactForm() {
            const [formData, setFormData] = React.useState({
                name: '',
                email: '',
                subject: '',
                message: ''
            });
            const [submitted, setSubmitted] = React.useState(false);

            const handleChange = (field, value) => {
                setFormData(prev => ({
                    ...prev,
                    [field]: value
                }));
            };

            const handleSubmit = (e) => {
                e.preventDefault();
                setSubmitted(true);
                // 実際のシステムでは、ここでAPIにデータを送信
                console.log('お問い合わせデータ:', formData);
            };

            if (submitted) {
                return (
                    <div>
                        <h3>お問い合わせありがとうございます</h3>
                        <div style={{ 
                            backgroundColor: '#d4edda', 
                            color: '#155724', 
                            padding: '15px', 
                            borderRadius: '5px',
                            marginBottom: '15px'
                        }}>
                            <p>お問い合わせを受け付けました。</p>
                            <p>内容を確認の上、2営業日以内にご連絡いたします。</p>
                        </div>
                        <button 
                            onClick={() => setSubmitted(false)}
                            style={{
                                backgroundColor: '#007bff',
                                color: 'white',
                                padding: '10px 20px',
                                border: 'none',
                                borderRadius: '5px',
                                cursor: 'pointer'
                            }}
                        >
                            新しいお問い合わせ
                        </button>
                    </div>
                );
            }

            return (
                <div>
                    <h3>お問い合わせフォーム</h3>
                    <form onSubmit={handleSubmit}>
                        <div style={{ marginBottom: '15px' }}>
                            <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
                                お名前
                            </label>
                            <input
                                type="text"
                                value={formData.name}
                                onChange={(e) => handleChange('name', e.target.value)}
                                style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
                                required
                            />
                        </div>
                        <div style={{ marginBottom: '15px' }}>
                            <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
                                メールアドレス
                            </label>
                            <input
                                type="email"
                                value={formData.email}
                                onChange={(e) => handleChange('email', e.target.value)}
                                style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
                                required
                            />
                        </div>
                        <div style={{ marginBottom: '15px' }}>
                            <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
                                件名
                            </label>
                            <select
                                value={formData.subject}
                                onChange={(e) => handleChange('subject', e.target.value)}
                                style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
                                required
                            >
                                <option value="">件名を選択</option>
                                <option value="サービスについて">サービスについて</option>
                                <option value="料金について">料金について</option>
                                <option value="技術相談">技術相談</option>
                                <option value="その他">その他</option>
                            </select>
                        </div>
                        <div style={{ marginBottom: '15px' }}>
                            <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
                                お問い合わせ内容
                            </label>
                            <textarea
                                value={formData.message}
                                onChange={(e) => handleChange('message', e.target.value)}
                                style={{ 
                                    width: '100%', 
                                    padding: '8px', 
                                    border: '1px solid #ddd', 
                                    borderRadius: '4px',
                                    minHeight: '100px'
                                }}
                                required
                            />
                        </div>
                        <button 
                            type="submit"
                            style={{
                                backgroundColor: '#007bff',
                                color: 'white',
                                padding: '10px 20px',
                                border: 'none',
                                borderRadius: '5px',
                                cursor: 'pointer',
                                fontSize: '16px'
                            }}
                        >
                            送信
                        </button>
                    </form>
                </div>
            );
        }

        // ニュースレター登録コンポーネント
        function NewsletterSignup() {
            const [email, setEmail] = React.useState('');
            const [subscribed, setSubscribed] = React.useState(false);

            const handleSubmit = (e) => {
                e.preventDefault();
                setSubscribed(true);
                // 実際のシステムでは、ここでAPIにデータを送信
                console.log('ニュースレター登録:', email);
            };

            return (
                <div>
                    <h3>ニュースレター登録</h3>
                    <p>最新の技術情報や、お得な情報をお届けします。</p>
                    {subscribed ? (
                        <div style={{ 
                            backgroundColor: '#d4edda', 
                            color: '#155724', 
                            padding: '15px', 
                            borderRadius: '5px'
                        }}>
                            <p>ニュースレターの登録が完了しました!</p>
                            <p>ご登録いただいたメールアドレスに確認メールを送信しました。</p>
                        </div>
                    ) : (
                        <form onSubmit={handleSubmit}>
                            <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
                                <input
                                    type="email"
                                    placeholder="メールアドレスを入力"
                                    value={email}
                                    onChange={(e) => setEmail(e.target.value)}
                                    style={{ 
                                        flex: 1, 
                                        padding: '10px', 
                                        border: '1px solid #ddd', 
                                        borderRadius: '4px' 
                                    }}
                                    required
                                />
                                <button 
                                    type="submit"
                                    style={{
                                        backgroundColor: '#28a745',
                                        color: 'white',
                                        padding: '10px 20px',
                                        border: 'none',
                                        borderRadius: '5px',
                                        cursor: 'pointer'
                                    }}
                                >
                                    登録
                                </button>
                            </div>
                        </form>
                    )}
                </div>
            );
        }

        // 各コンポーネントを対応する要素にレンダリング
        const contactRoot = ReactDOM.createRoot(document.getElementById('contact-form'));
        contactRoot.render(<ContactForm />);

        const newsletterRoot = ReactDOM.createRoot(document.getElementById('newsletter-signup'));
        newsletterRoot.render(<NewsletterSignup />);
    </script>
</body>
</html>

この例では、以下の点が重要です。

既存コンテンツとの共存

従来のHTMLコンテンツとReactコンポーネントが同じページに共存しています。 段階的な導入が可能なんです。

複数のReactコンポーネント

同じページに複数のReactコンポーネントを配置しています。 それぞれが独立して動作します。

実用的な機能

お問い合わせフォームとニュースレター登録という、実際のWebサイトでよく使われる機能を実装しています。

活用場面のまとめ

React CDNは、以下のような場面で特に有効です。

  • 学習・練習: Reactの基本を学ぶ
  • プロトタイプ作成: アイデアを素早く形にする
  • 既存サイト改善: 部分的にReactを導入する
  • デモ・プレゼン: 動的なデモを作成する
  • 教育現場: 授業やワークショップで使用する

「こんなに簡単にできるなんて」と思うかもしれませんが、これがCDNの力なんです。

CDNの制限と本格開発への移行

React CDNは素晴らしいですが、いくつかの制限もあります。

CDNの制限事項

CDNを使用する際の主な制限を理解しておきましょう。

パフォーマンス面の制限

  • ビルド最適化なし: コードの圧縮・最適化が行われない
  • 大規模アプリに不向き: ファイルサイズが大きくなると動作が重くなる
  • キャッシュ効率: 自分のコードがキャッシュされない

開発効率の制限

  • エラー検出: TypeScriptなどの型チェックが使えない
  • コード補完: IDEでの支援機能が限定的
  • デバッグ: 開発者ツールでのデバッグが困難

機能面の制限

  • モジュール管理: ES6 modulesが使えない
  • パッケージ管理: npmの豊富なパッケージが使いにくい
  • ビルドツール: WebpackやViteなどのツールが使えない

本格開発への移行タイミング

以下のような場合は、本格的な開発環境への移行を検討しましょう。

移行を検討すべき場面

  • アプリが大きくなった: 複数のページや機能が必要
  • チーム開発: 複数人での開発が必要
  • 本番運用: 実際のサービスとして運用する
  • パフォーマンス重視: 高速な動作が求められる

移行の手順

本格的な開発環境への移行は、以下の手順で行います。

# 1. Node.jsプロジェクトの作成
npx create-react-app my-app

# 2. プロジェクトディレクトリに移動
cd my-app

# 3. 開発サーバーの起動
npm start

# 4. 本番ビルド
npm run build

CDNから本格環境への移行例

CDNで作成したコンポーネントを本格的な環境に移行する方法を見てみましょう。

CDN版のコード

<!-- CDN版 -->
<script type="text/babel">
    function TodoApp() {
        const [todos, setTodos] = React.useState([]);
        const [inputValue, setInputValue] = React.useState('');

        const addTodo = () => {
            if (inputValue.trim() !== '') {
                const newTodo = {
                    id: Date.now(),
                    text: inputValue.trim(),
                    completed: false
                };
                setTodos([...todos, newTodo]);
                setInputValue('');
            }
        };

        // ... その他の関数

        return (
            <div className="todo-app">
                <h1>React ToDoリスト</h1>
                {/* ... JSX */}
            </div>
        );
    }

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<TodoApp />);
</script>

本格版のコード

// TodoApp.jsx (本格版)
import React, { useState } from 'react';
import './TodoApp.css';

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

    const addTodo = () => {
        if (inputValue.trim() !== '') {
            const newTodo = {
                id: Date.now(),
                text: inputValue.trim(),
                completed: false
            };
            setTodos([...todos, newTodo]);
            setInputValue('');
        }
    };

    // ... その他の関数(同じ)

    return (
        <div className="todo-app">
            <h1>React ToDoリスト</h1>
            {/* ... JSX(同じ) */}
        </div>
    );
}

export default TodoApp;

移行のポイント

  1. import文の追加: import React from 'react'
  2. CSS分離: スタイルを別ファイルに移動
  3. export追加: export default TodoApp
  4. ファイル分割: 複数のファイルに分割

移行時の注意点

移行時には、以下の点に注意してください。

開発環境の違い

  • エラー表示: より詳細なエラーメッセージ
  • ホットリロード: 変更が即座に反映される
  • TypeScript: 型チェックが可能

ビルドプロセス

  • 最適化: 本番用にコードが最適化される
  • バンドル: 複数のファイルが1つにまとめられる
  • Tree Shaking: 未使用のコードが削除される

CDNで学んだ知識は、本格的な開発環境でもそのまま活用できます。

「移行は難しそう」と思うかもしれませんが、基本的な概念は同じです。

まとめ

React CDNを使った開発方法について、基本から応用まで詳しく解説しました。

React CDNの主なメリット

あらためて、React CDNの特徴をまとめてみましょう。

  • 簡単な開始: HTMLファイル1つですぐに始められる
  • 学習に最適: 複雑な設定なしでReactの基本を学習
  • プロトタイプ作成: アイデアを素早く形にできる
  • 部分導入: 既存サイトに段階的にReactを導入
  • 他ライブラリとの組み合わせ: Bootstrap、Chart.jsなどと簡単に連携

効果的な学習の進め方

React CDNでの学習は、以下の順序で進めることをおすすめします。

  1. 基本のHello World: JSXとコンポーネントの理解
  2. 状態管理: useStateを使った動的なコンポーネント
  3. イベント処理: ユーザーとのインタラクション
  4. リスト表示: 配列データの表示と操作
  5. コンポーネント分割: 再利用可能な設計
  6. 外部ライブラリ: 他のCDNライブラリとの組み合わせ

実践的な活用方法

React CDNは、以下のような場面で特に力を発揮します。

  • 学習・練習: Reactの基本概念を理解する
  • プロトタイプ作成: 新しいアイデアを素早く試す
  • 教育現場: 授業やワークショップでの活用
  • 既存サイト改善: 部分的なインタラクティブ機能の追加

次のステップ

CDNでReactの基本を理解したら、以下のステップに進むことをおすすめします。

  1. 本格的な開発環境: create-react-appの使用
  2. 状態管理ライブラリ: Redux、Zustandなどの学習
  3. ルーティング: React Routerの活用
  4. TypeScript: 型安全な開発
  5. テスト: Jest、React Testing Libraryの使用

最後に

React CDNは、React学習の入り口として非常に優れた選択肢です。 複雑な環境構築に時間を取られることなく、すぐにReactの魅力を体験できます。

「プログラミングは難しそう」と思っていた方でも、HTMLファイル1つから始められるので、きっと楽しく学習できるはずです。

まずは、この記事で紹介したサンプルコードを実際に動かしてみてください。 そして、少しずつ改造してみてください。

慣れてきたら、本格的な開発環境に移行することで、より高度なReactアプリケーションを作れるようになります。

ぜひ、HTMLファイル1つから始めて、Reactの素晴らしい世界を体験してみてください。

あなたのReact学習の成功を、心から応援しています!

関連記事