次の方法で共有


Visual Studio で React アプリを作成する

このチュートリアルでは、JavaScript と Visual Studio 2022 を使用して、to-do リスト Web アプリ用の React フロントエンドを作成します。 このアプリのコードは ToDoJSWebApp にあります。

[前提条件]

次のコードをインストールしてください。

React ToDo リスト アプリを作成する

  1. Visual Studio で、[ ファイル] > [新しい > プロジェクト ] を選択して [新しいプロジェクトの作成] ダイアログを開き、 React App JavaScript テンプレートを選択して、[ 次へ] を選択します。

    テンプレートの選択を示すスクリーンショット。

  2. プロジェクトに TodoWebApp 名前を付け、[ 作成] を選択します。

    これにより、 vite コマンド ライン ツールを使用して JavaScript プロジェクトが作成されます。

  3. ソリューション エクスプローラーで、 src フォルダーを右クリックし、[ 新しいフォルダー > 追加] を選択します。 componentsという名前の新しいフォルダーを作成します。

    コンポーネントをコンポーネント フォルダーに配置することは一般的な規則ですが、これは必須ではありません。

  4. 新しいフォルダーを右クリックし、[ 新しい項目 > 追加] を選択し、ダイアログ ボックスで [React JSX コンポーネント ファイル ] を選択し、 TodoList名前を付 けて、[追加] をクリックします。

    JSX コンポーネントの追加を示すスクリーンショット。

    これにより、コンポーネント フォルダーに新しい JSX ファイルが作成されます。

  5. TodoList コンポーネントを開き、既定のコンテンツを次のように置き換えます。

    function TodoList() {
      return (
        <h2>TODO app contents</h2>
      );
    }
    

    このコンポーネントにはヘッダーが表示され、後で置き換えます。

    次に、アプリでこのコンポーネントを接続します。 App.jsx は、読み込まれるメイン コンポーネントであり、to-do リスト アプリケーションを表します。 このコンポーネントは、 main.jsx ファイルで使用されます。

  6. ソリューション エクスプローラーで、 App.jsxを開き、先頭からすべてのインポートを削除し、return ステートメントの内容をクリアします。 ファイルは次のようになります。

    function App() {
      return (
        <>
          <TodoList />
        </>
      );
    }
    export default App;
    
  7. TodoList コンポーネントを追加するには、フラグメント内にカーソルを置き、「 <TodoL RETURN」と入力します。 これにより、コンポーネントと import ステートメントが追加されます。

    アプリへの JSX コンポーネントの追加を示すスクリーンショット。

    次に、CSS ファイルをクリアします。

  8. App.cssを開き、すべてのコンテンツを削除します。

  9. Index.cssを開き、:rootのスタイルを除くすべてのコンテンツを削除します。

    :root {
      font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
      line-height: 1.5;
      font-weight: 400;
      color-scheme: light dark;
      color: rgba(255, 255, 255, 0.87);
      background-color: #242424;
    }
    

アプリを実行する

ツール バーから [ デバッグの開始 ] ボタンを選択するか、F5 キーを押します。

アプリがブラウザー ウィンドウで開きます。

ブラウザーで実行されているアプリを示すスクリーンショット。

to-do リスト関数をアプリに追加する

アプリは実行したままにしておくことができます。 変更すると、Vite のホット モジュール交換サポートを使用して、アプリが最新のコンテンツで自動的に更新されます。 フォルダーの追加やファイル名の変更などの一部のアクションでは、デバッグを停止してからアプリを再起動する必要がありますが、一般に、アプリの開発時にバックグラウンドで実行したままにすることができます。 TodoList.jsx コンポーネントを開いて、定義を開始できるようにします。

  1. ソリューション エクスプローラーで、 TodoList.jsx を開き、to-do リスト エントリの表示と管理に必要な UI を追加します。 コンテンツを次のコードに置き換えます。

    function TodoList() {
      return (
        <div>
          <h1>TODO</h1>
          <div>
            <input type="text" placeholder="Enter a task" required aria-label="Task text" />
            <button className="add-button" aria-label="Add task">Add</button>
          </div>
          <ol id="todo-list">
            <p>existing tasks will be shown here</p>
          </ol>
        </div>
      );
    }
    export default TodoList;
    

    上記のコードは、新しい to-do タスクの入力ボックスと、入力を送信するボタンを追加します。 次に、[追加] ボタンを接続します。 useState React フックを使用して、2 つの状態変数 (1 つは追加されるタスク用、もう 1 つは既存のタスクを格納する) を追加します。 このチュートリアルでは、タスクは永続ストレージではなくメモリに格納されます。

  2. useStateをインポートするTodoList.jsxに、次の import ステートメントを追加します。

    import { useState } from 'react'
    
  3. 次に、そのフックを使用して状態変数を作成します。 return ステートメントの上の TodoList 関数に次のコードを追加します。

    const [tasks, setTasks] = useState(["Drink some coffee", "Create a TODO app", "Drink some more coffee"]);
    const [newTaskText, setNewTaskText] = useState("");
    

    これにより、データに対して 2 つの変数 ( tasksnewTaskText) と、それらの変数を更新するために呼び出すことができる 2 つの関数 ( setTaskssetNewTasks) が設定されます。 状態変数の値が変更されると、React によってコンポーネントが自動的に再レンダリングされます。

    TodoList.jsx を更新して to-do 項目を一覧として表示する準備は整いましたが、最初に学習する重要な React の概念があります。

    React では、項目の一覧を表示するときに、リスト内の各項目を一意に識別するためのキーを追加する必要があります。 この機能については、 レンダリング リストの React ドキュメントで詳しく説明されていますが、ここでは基本について学習します。 表示する to-do 項目の一覧があり、項目ごとに一意のキーを関連付ける必要があります。 各項目のキーは変更しないでください。このため、配列内の項目のインデックスをキーとして使用することはできません。 これらの値の有効期間中は変更されない ID が必要です。 randomUUID() を使用して、to-do 項目ごとに一意の ID を作成します。

  4. 各 to-do 項目のキーとして UUID を使用して TodoList.jsx を作成します。 次のコードで TodoList.jsx を更新します。

    import React, { useState } from 'react';
    
    const initialTasks = [
        { id: self.crypto.randomUUID(), text: 'Drink some coffee' },
        { id: self.crypto.randomUUID(), text: 'Create a TODO app' },
        { id: self.crypto.randomUUID(), text: 'Drink some more coffee' }
    ];
    
    function TodoList() {
        const [tasks, setTasks] = useState(initialTasks);
        const [newTaskText, setNewTaskText] = useState("");
    
        return (
            <article
                className="todo-list"
                aria-label="task list manager">
                <header>
                    <h1>TODO</h1>
                    <form className="todo-input" aria-controls="todo-list">
                        <input
                            type="text"
                            placeholder="Enter a task"
                            value={newTaskText} />
                        <button
                            className="add-button">
                            Add
                        </button>
                    </form>
                </header>
                <ol id="todo-list" aria-live="polite" aria-label="task list">
                    {tasks.map((task, index) =>
                        <li key={task.id}>
                            <span className="text">{task.text}</span>
                        </li>
                    )}
                </ol>
            </article>
        );
    }
    export default TodoList;
    

    ID 値は TodoList 関数の外部で割り当てられるため、ページが再レンダリングされた場合に値が変わらないことを確認できます。 この状態でアプリを試してみると、todo 入力要素に入力できないことに気付きます。 これは、入力要素が空の文字列に初期化された newTaskTextにバインドされているためです。 ユーザーが新しいタスクを追加できるようにするには、そのコントロールの onChange イベントを処理する必要があります。 [追加] ボタンのサポートも実装する必要があります。

  5. TodoList 関数の return ステートメントの直前に必要な関数を追加します。

    function handleInputChange(event) {
        setNewTaskText(event.target.value);
    }
    
    function addTask() {
        if (newTaskText.trim() !== "") {
            setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]);
            setNewTaskText("");
        }
        event.preventDefault();
    }
    

    handleInputChanged関数では、入力フィールドの新しい値がevent.target.valueを介して渡され、その値を使用してnewTaskText変数の値がsetNewTaskTextで更新されます。 addTask関数で、setTasksを使用して既存のタスクの一覧に新しいタスクを追加し、項目の ID を新しい UUID 値として設定します。 onChange={handleInputChange}を含むように入力要素を更新し、[追加] ボタンを更新してonClick={addTask}を含めます。 このコードは、そのイベントを処理する関数にイベントを結び付けます。 この後、新しいタスクをタスク リストに追加できます。 新しいタスクが一覧の下部に追加されます。 このアプリをより便利にするには、タスクを削除したり、タスクを上下に移動したりするためのサポートを追加する必要があります。

  6. 削除、上への移動、下への移動をサポートする関数を追加し、各アクションのボタンを表示するようにマークアップを更新します。 return ステートメントの上にある TodoList 関数に次のコードを追加します。

    function deleteTask(id) {
        const updatedTasks = tasks.filter(task => task.id != id);
        setTasks(updatedTasks);
    }
    
    function moveTaskUp(index) {
        if (index > 0) {
            const updatedTasks = [...tasks];
            [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]];
            setTasks(updatedTasks);
        }
    }
    
    function moveTaskDown(index) {
        if (index < tasks.length) {
            const updatedTasks = [...tasks];
            [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]];
            setTasks(updatedTasks);
        }
    }
    

    delete 関数はタスク ID を取り込み、その ID をリストから削除し、 Array filter() メソッドを使用して、選択した項目を除く新しい配列を作成し、 setTasks()を呼び出します。 この作業はアイテムの順序に固有であるため、他の 2 つの関数はアイテムのインデックスを取ります。 moveTaskUp()moveTaskDown()配列分割代入を使用して、選択したタスクをその隣のタスクと入れ替えます。

  7. 次に、これら 3 つのボタンを含むようにビューを更新します。 return ステートメントを更新して、次の内容を含めます。

    return (
        <article
            className="todo-list"
            aria-label="task list manager">
            <header>
                <h1>TODO</h1>
                <form className="todo-input" onSubmit={addTask} aria-controls="todo-list">
                    <input
                        type="text"
                        required
                        autoFocus
                        placeholder="Enter a task"
                        value={newTaskText}
                        aria-label="Task text"
                        onChange={handleInputChange} />
                    <button
                        className="add-button"
                        aria-label="Add task">
                        Add
                    </button>
                </form>
            </header>
            <ol id="todo-list" aria-live="polite">
                {tasks.map((task, index) =>
                    <li key={task.id}>
                        <span className="text">{task.text}</span>
                        <button className="delete-button" onClick={() => deleteTask(task.id)}>
                            🗑️
                        </button>
                        <button className="up-button" onClick={() => moveTaskUp(index)}>
                            ⇧
                        </button>
                        <button className="down-button" onClick={() => moveTaskDown(index)}>
                            ⇩
                        </button>
                    </li>
                )}
            </ol>
        </article>
    );
    

    前に説明したタスクを実行するために必要なボタンを追加しました。 ボタンのアイコンとして Unicode 文字を使用しています。 マークアップには、後で CSS の追加をサポートするためにいくつかの属性が追加されています。 aria 属性を使用してアクセシビリティを向上させることもできます。これは省略可能ですが、強くお勧めします。 アプリを実行すると、次の図のようになります。

    実行中のアプリと一覧を示すスクリーンショット

    これで、TODO Web アプリで次の操作を実行できるようになります。

    • タスクの追加
    • タスクを削除
    • タスクを上へ移動
    • タスクを下へ移動

    これらの関数は機能しますが、リファクタリングして再利用可能なコンポーネントをビルドし、to-do 項目を表示することができます。 to-do 項目のマークアップは、新しいコンポーネントである TodoItem に入ります。 リストの管理は Todo コンポーネントに保持されるため、[削除] ボタンと [移動] ボタンにコールバックを渡すことができます。

  8. 作業を開始するには、ソリューション エクスプローラーで コンポーネント フォルダーを右クリックし、[ 新しい項目 > 追加] を選択します。

  9. 開いたダイアログで React JSX コンポーネント ファイルを選択し、TodoItem という名前を 付けて、[追加] を選択します。

  10. 次のコードを TodoItem に追加します。

    このコードでは、タスクとコールバックをプロパティとしてこの新しいコンポーネントに渡します。

    import PropTypes from 'prop-types';
    
    function TodoItem({ task, deleteTaskCallback, moveTaskUpCallback, moveTaskDownCallback }) {
        return (
            <li aria-label="task" >
                <span className="text">{task}</span>
                <button
                    type="button"
                    aria-label="Delete task"
                    className="delete-button"
                    onClick={() => deleteTaskCallback()}>
                    🗑️
                </button>
                <button
                    type="button"
                    aria-label="Move task up"
                    className="up-button"
                    onClick={() => moveTaskUpCallback()}>
                    ⇧
                </button>
                <button
                    type="button"
                    aria-label="Move task down"
                    className="down-button"
                    onClick={() => moveTaskDownCallback()}>
                    ⇩
                </button>
            </li>
        );
    }
    
    TodoItem.propTypes = {
        task: PropTypes.string.isRequired,
        deleteTaskCallback: PropTypes.func.isRequired,
        moveTaskUpCallback: PropTypes.func.isRequired,
        moveTaskDownCallback: PropTypes.func.isRequired,
    };
    
    export default TodoItem;
    

    上記のコードには Todo コンポーネントのマークアップが含まれており、その最後に PropTypesを宣言します。 プロパティは、親コンポーネントから子コンポーネントにデータを渡すために使用されます。 小道具の詳細については、「 コンポーネントへの小道具の受け渡し - React」を参照してください。 削除関数と移動関数はコールバックとして渡されるため、これらのコールバックを呼び出すには、 onClick ハンドラーを更新する必要があります。

  11. 必要なコードを追加します。 TodoItem コンポーネントを使用する TodoList の完全なコードを次に示します。

    import React, { useState } from 'react'
    import TodoItem from './TodoItem'
    
    const initialTasks = [
        { id: self.crypto.randomUUID(), text: 'Drink some coffee' },
        { id: self.crypto.randomUUID(), text: 'Create a TODO app' },
        { id: self.crypto.randomUUID(), text: 'Drink some more coffee' }
    ];
    
    function TodoList() {
        const [tasks, setTasks] = useState(initialTasks);
        const [newTaskText, setNewTaskText] = useState("");
        function handleInputChange(event) {
            setNewTaskText(event.target.value);
        }
        function addTask() {
            if (newTaskText.trim() !== "") {
                setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]);
                setNewTaskText("");
            }
            event.preventDefault();
        }
        function deleteTask(id) {
            const updatedTasks = tasks.filter(task => task.id !== id);
            setTasks(updatedTasks);
        }
        function moveTaskUp(index) {
            if (index > 0) {
                const updatedTasks = [...tasks];
                [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]];
                setTasks(updatedTasks);
            }
        }
        function moveTaskDown(index) {
            if (index < tasks.length) {
                const updatedTasks = [...tasks];
                [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]];
                setTasks(updatedTasks);
            }
        }
        return (
            <article
                className="todo-list"
                aria-label="task list manager">
                <header>
                    <h1>TODO</h1>
                    <form onSubmit={addTask} aria-controls="todo-list">
                        <input
                            type="text"
                            required
                            placeholder="Enter a task"
                            value={newTaskText}
                            aria-label="Task text"
                            onChange={handleInputChange} />
                        <button
                            className="add-button"
                            aria-label="Add task">
                            Add
                        </button>
                    </form>
                </header>
                <ol id="todo-list" aria-live="polite">
                    {tasks.map((task, index) =>
                        <TodoItem
                            key={task.id}
                            task={task.text}
                            deleteTaskCallback={() => deleteTask(task.id)}
                            moveTaskUpCallback={() => moveTaskUp(index)}
                            moveTaskDownCallback={() => moveTaskDown(index)}
                        />
                    )}
                </ol>
            </article>
        );
    }
    
    export default TodoList;
    

    ここで、TodoItem コンポーネントを使用して、各 to-do 項目をレンダリングします。 キーが task.id に設定されていることに注意してください。このキーには、そのタスクの UUID 値が含まれています。 アプリを実行するときに、TodoItem を使用するようにリファクタリングしたため、アプリの外観や動作に対する変更は表示されません。

    すべての基本的な関数がサポートされたので、外観を良くするために、これにスタイルを追加します。 まず、このアプリで使用するフォント ファミリ Inter の Index.html にリンクを追加します。 Index.htmlでは、クリーンアップする必要があるその他の項目がいくつかあります。 具体的には、タイトルを更新し、現在アイコンとして使用されている vite.svg ファイルを置き換える必要があります。

  12. 次の内容でIndex.html を更新します。

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <link rel="icon" type="image/svg+xml" href="/checkmark-square.svg" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>TODO app</title>
            <link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet'>
            <script type="module" defer src="/src/main.jsx"></script>
        </head>
        <body>
        </body>
    </html>
    
  13. main.jsx ファイルを編集して、createRootを呼び出すときにrootmainに置き換えます。

    main.jsx の完全なコードを次に示します。

    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import App from './App.jsx'
    import './index.css'
    
    createRoot(document.querySelector('main')).render(
        <StrictMode>
            <App />
        </StrictMode>,
    )
    

    これらの変更に加えて、 checkmark-square.svg ファイルがパブリック フォルダーに追加されました。 これは FluentUI チェックマークの正方形の画像からの SVG であり、直接ダウンロードできます。 (より統合されたエクスペリエンスに使用できるパッケージがありますが、この記事の範囲外です)。

    次に、TodoList コンポーネントのスタイルを更新します。

  14. components フォルダーに、 TodoList.cssという名前の新しい CSS ファイルを追加します。 プロジェクトを右クリックし、[ 新しい項目 > 追加 ] を選択し、[ スタイル シート] を選択します。 ファイルにTodoList.css名前を けます。

  15. 次のコードを TodoList.cssに追加します。

    .todo-list {
        background-color: #1e1e1e;
        padding: 1.25rem;
        border-radius: 0.5rem;
        box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.3);
        width: 100%;
        max-width: 25rem;
    }
    
    .todo-list h1 {
        text-align: center;
        color: #e0e0e0;
    }
    
    .todo-input {
        display: flex;
        justify-content: space-between;
        margin-bottom: 1.25rem;
    }
    
    .todo-input input {
        flex: 1;
        padding: 0.625rem;
        border: 0.0625rem solid #333;
        border-radius: 0.25rem;
        margin-right: 0.625rem;
        background-color: #2c2c2c;
        color: #e0e0e0;
    }
    
    .todo-input .add-button {
        padding: 0.625rem 1.25rem;
        background-color: #007bff;
        color: #fff;
        border: none;
        border-radius: 0.25rem;
        cursor: pointer;
    }
    
    .todo-input .add-button:hover {
        background-color: #0056b3;
    }
    
    .todo-list ol {
        list-style-type: none;
        padding: 0;
    }
    
    .todo-list li {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0.625rem;
        border-bottom: 0.0625rem solid #333;
    }
    
    .todo-list li:last-child {
        border-bottom: none;
    }
    
    .todo-list .text {
        flex: 1;
    }
    
    .todo-list li button {
        background: none;
        border: none;
        cursor: pointer;
        font-size: 1rem;
        margin-left: 0.625rem;
        color: #e0e0e0;
    }
    
    .todo-list li button:hover {
        color: #007bff;
    }
    
    .todo-list li button.delete-button {
        color: #ff4d4d;
    }
    
    .todo-list li button.up-button,
    .todo-list li button.down-button {
        color: #4caf50;
    }
    
  16. 次に、 TodoList.jsx を編集して、ファイルの先頭に次のインポートを追加します。

    import './TodoList.css';
    
  17. 変更を保存した後、ブラウザーを更新します。 これにより、アプリのスタイルが向上します。 アプリは次のようになります。

    実行されているアプリの最終バージョンを示すスクリーンショット。

    これで、to-do 項目をメモリに格納する作業 to-do リスト アプリが作成されました。 この時点から、to-do 項目を localStorage/IndexedDb に格納するようにアプリを更新するか、サーバー側データベースまたはその他のバックエンドと統合して、より永続的なストレージを得ることができました。

概要

このチュートリアルでは、Visual Studio を使用して新しい React アプリを作成しました。 アプリは、タスクの追加、タスクの削除、並べ替えのサポートを含む to-do リストで構成されます。 2 つの新しい React コンポーネントを作成し、このチュートリアル全体で使用しました。

リソース