共用方式為


在 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的新資料夾。

    將元件放在 components 資料夾中是常見的慣例,但這並非必要。

  4. 以滑鼠右鍵按兩下新資料夾,選取 [新增 > React JSX 元件檔案]、將它命名為 TodoList,然後按兩下 [新增]。

    顯示新增 JSX 元件的螢幕快照。

    這會在 components 資料夾中建立新的 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. 將下列 import 語句新增至 TodoList.jsx,以匯入 useState

    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("");
    

    這會為資料和兩個函式設定兩個變數,tasksnewTaskText,您可以呼叫這些變數來更新這些變數,setTaskssetNewTasks。 當狀態變數的值變更時,React 會自動重新轉譯元件。

    您幾乎已準備好更新 TodoList.jsx,將 to-do 項目顯示為清單,但必須先瞭解重要的 React 概念。

    在 React 中,當您顯示項目清單時,您需要新增鍵以唯一識別清單中的每個項目。 這項功能會在 轉譯清單的 React 檔中深入說明,但在這裡您將瞭解基本概念。 您有一份要顯示的 to-do 項清單,而且您需要為每個項配對一個唯一的鍵。 每個專案的索引鍵不應該變更,因此您無法使用陣列中專案的索引做為索引鍵。 您需要不會在這些值存留期內變更的識別碼。 您將使用 randomUUID() 為每個 to-do 項目創建唯一識別碼。

  4. 使用 UUID 作為每個 to-do 專案的索引鍵,建立 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;
    

    由於標識碼值是在 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 將新工作新增至現有工作清單,並將專案的標識碼設定為新的 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 函式會接受工作識別符,並從清單中刪除該工作識別碼,並使用 Array filter() 方法來建立排除選取專案的新陣列,然後呼叫 setTasks()。 其他兩個函式會接受項目的索引,因為這項工作是項目排序特定的。 moveTaskUp()moveTaskDown() 都使用 陣列解構賦值,將選取的工作與其鄰居交換。

  7. 接下來,更新檢視以包含這三個按鈕。 更新 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。 由於清單的管理會保留在待辦事項元件中,因此您可以將回呼傳遞至 [刪除] 和 [移動] 按鈕。

  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。 屬性可用來將數據從父元件傳遞至子元件。 如需有關 Props 的更多資訊,請參閱 將 Props 傳遞至元件—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。

    既然您已支援所有基本函式,現在可以開始為此新增一些樣式,使其看起來不錯。 首先,在 Index.html 中新增字型系列 Inter 的連結,以供此應用程式使用。 在 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 檔案,以在呼叫 root時,將 main 取代為 createRoot

    這裡會顯示 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 /,或將此專案與伺服器端資料庫或其他後端整合,以取得更永久的記憶體。

總結

在本教學課程中,您已使用 Visual Studio 建立新的 React 應用程式。 應用程式是由 to-do 清單所組成,其中包含新增工作、刪除工作,以及重新排序工作的支援。 您已建立兩個新的 React 元件,並在本教學課程中使用這些元件。

資源