在 Visual Studio 中建立 React 應用程式
在本教學課程中,您會使用 JavaScript 和 Visual Studio 2022,為 to-do 清單 Web 應用程式建立 React 前端。 您可以在 ToDoJSWebApp找到此應用程式的程式代碼。
先決條件
請務必安裝下列項目:
- Visual Studio 2022 或更新版本。 移至 Visual Studio 下載 頁面免費安裝。
- npm (
https://www.npmjs.com/
),其中包含 Node.js。
建立 React ToDo 列表應用程式
在 Visual Studio 中,選取 [檔案] > [新增 > 專案] 開啟 [建立新專案] 對話框,選取 [React App JavaScript 範本],然後選擇 [下一步]。
將專案命名為
TodoWebApp
,然後選取 [建立]。這會使用 vite命令行工具建立JavaScript專案。
在 [方案總管] 中,以滑鼠右鍵按一下 [
src
] 資料夾,然後選擇 [新增 > 資料夾]。 並建立名為components
的新資料夾。將元件放在 components 資料夾中是常見的慣例,但這並非必要。
以滑鼠右鍵按兩下新資料夾,選取 [新增 > React JSX 元件檔案]、將它命名為
TodoList
,然後按兩下 [新增]。這會在 components 資料夾中建立新的 JSX 檔案。
開啟
TodoList
元件,並以下列內容取代預設內容:function TodoList() { return ( <h2>TODO app contents</h2> ); }
此元件會顯示標頭,您稍後將會加以取代。
接下來,在應用程式中連接此元件。
App.jsx
是載入的主要元件,代表 to-do 清單應用程式。 此元件用於main.jsx
檔案中。在 [方案總管] 中,開啟 [
App.jsx
],從頂端移除所有匯入,並清除 return 語句的內容。 檔案看起來應該如下所示。function App() { return ( <> <TodoList /> </> ); } export default App;
若要新增 TodoList 元件,請將游標放在片段內,然後輸入
<TodoL RETURN
。 這會新增元件和import語句。接下來,清除 CSS 檔案。
開啟
App.css
並刪除所有內容。開啟
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
元件,以便我們可以開始定義它。
在 [方案總管] 中,開啟
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 攔截來新增兩個狀態變數、一個用於新增的工作,另一個用於儲存現有工作。 在本教學課程中,工作會儲存在記憶體中,而不是持久性儲存。
將下列 import 語句新增至
TodoList.jsx
,以匯入useState
。import { useState } from 'react'
接下來,使用該鉤子來建立狀態變數。 在 return 語句上方的
TodoList
函式中新增下列程式代碼。const [tasks, setTasks] = useState(["Drink some coffee", "Create a TODO app", "Drink some more coffee"]); const [newTaskText, setNewTaskText] = useState("");
這會為資料和兩個函式設定兩個變數,
tasks
和newTaskText
,您可以呼叫這些變數來更新這些變數,setTasks
和setNewTasks
。 當狀態變數的值變更時,React 會自動重新轉譯元件。您幾乎已準備好更新 TodoList.jsx,將 to-do 項目顯示為清單,但必須先瞭解重要的 React 概念。
在 React 中,當您顯示項目清單時,您需要新增鍵以唯一識別清單中的每個項目。 這項功能會在 轉譯清單的 React 檔中深入說明,但在這裡您將瞭解基本概念。 您有一份要顯示的 to-do 項清單,而且您需要為每個項配對一個唯一的鍵。 每個專案的索引鍵不應該變更,因此您無法使用陣列中專案的索引做為索引鍵。 您需要不會在這些值存留期內變更的識別碼。 您將使用 randomUUID() 為每個 to-do 項目創建唯一識別碼。
使用 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
事件。 您也需要實作 [新增] 按鈕支援。在 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}
。 此程式代碼會將事件連線至處理該事件的函式。 在此之後,您應該能夠將新工作新增至工作清單。 新工作會新增至任務清單底部。 若要讓此應用程式更實用,您必須新增支援以刪除工作,以及將工作向上或向下移動。新增函式以支援刪除、上移和下移,然後更新標記以顯示每個動作的按鈕。 在 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()
都使用 陣列解構賦值,將選取的工作與其鄰居交換。接下來,更新檢視以包含這三個按鈕。 更新 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。 由於清單的管理會保留在待辦事項元件中,因此您可以將回呼傳遞至 [刪除] 和 [移動] 按鈕。
若要開始使用,請以滑鼠右鍵按一下 [方案總管] 中 元件 資料夾,然後選取 [新增 > 新項目]。
在開啟的對話框中,選取 [React JSX 元件檔案],並將其命名為 TodoItem,然後點選 [新增]。
將下列程式代碼新增至 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
處理程式,才能呼叫這些回呼。新增必要的程序代碼。 這裡會顯示使用 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 檔案。
使用下列內容更新 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>
編輯 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 元件的樣式。
在 components 資料夾中,新增名為 TodoList.css的新 CSS 檔案。 您可以對專案滑鼠右鍵單擊,然後選取 [新增 > 新項目],然後選取 [樣式表單]。 提供檔案名 TodoList.css。
將下列程式代碼新增至 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; }
接下來,編輯 TodoList.jsx,以在檔案頂端新增下列匯入。
import './TodoList.css';
請在儲存變更之後重新整理瀏覽器。 這應該可改善應用程式的樣式。 應用程式看起來應該如下所示。
現在您已建置一個可運作的 to-do 清單應用程式,將 to-do 項目儲存在記憶體中。 此時,您可以更新應用程式,將 to-do 項目儲存在localStorage /,或將此專案與伺服器端資料庫或其他後端整合,以取得更永久的記憶體。
總結
在本教學課程中,您已使用 Visual Studio 建立新的 React 應用程式。 應用程式是由 to-do 清單所組成,其中包含新增工作、刪除工作,以及重新排序工作的支援。 您已建立兩個新的 React 元件,並在本教學課程中使用這些元件。