Visual Studio에서 React 앱 만들기
이 자습서에서는 JavaScript 및 Visual Studio 2022를 사용하여 to-do 목록 웹앱에 대한 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
새 폴더를 만듭니다.구성 요소 폴더에 구성 요소를 배치하는 일반적인 규칙이지만 필수는 아닙니다.
새 폴더를 오른쪽 클릭하여 Add > React JSX Component File을 선택한 다음 이름을
TodoList
으로 지정하고 Add을 클릭합니다.JSX 구성 요소 추가를 보여 주는
그러면 구성 요소 폴더에 새 JSX 파일이 만들어집니다.
TodoList
구성 요소를 열고 기본 콘텐츠를 다음으로 바꿉다.function TodoList() { return ( <h2>TODO app contents</h2> ); }
이 구성 요소는 나중에 바꿀 헤더를 표시합니다.
다음으로, 앱에서 이 구성 요소를 연결합니다.
App.jsx
to-do 목록 애플리케이션을 나타내는 로드되는 주 구성 요소입니다. 이 구성 요소는main.jsx
파일에 사용됩니다.솔루션 탐색기에서
App.jsx
열고, 위쪽에서 모든 가져오기를 제거하고, 반환 문의 내용을 지웁니다. 파일은 다음과 같습니다.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 후크를 사용하여 두 개의 상태 변수를 추가합니다. 하나는 추가되는 작업에 대한 변수이고 다른 하나는 기존 작업을 저장합니다. 이 자습서에서는 태스크가 영구 스토리지가 아닌 메모리에 저장됩니다.
TodoList.jsx
에 다음의 import 문을 추가하여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는 구성 요소를 자동으로 다시 렌더링합니다.to-do 항목을 목록으로 표시하도록 TodoList.jsx 업데이트할 준비가 거의 완료되었지만 먼저 학습해야 하는 중요한 React 개념이 있습니다.
React에서 항목 목록을 표시할 때 목록의 각 항목을 고유하게 식별하는 키를 추가해야 합니다. 이 기능은 렌더링 목록React 문서에 자세히 설명되어 있지만 여기서는 기본 사항을 알아봅니다. 표시할 to-do 항목 목록이 있으며 각 항목에 대해 고유한 키를 연결해야 합니다. 각 항목에 대한 키는 변경되지 않아야 하므로 배열에 있는 항목의 인덱스도 키로 사용할 수 없습니다. 이러한 값의 수명 동안 변경되지 않는 ID가 필요합니다. randomUUID() 사용하여 각 to-do 항목에 대한 고유 ID를 만듭니다.
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;
ID 값은 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
사용하여 기존 작업 목록에 새 작업을 추가하고 항목의 ID를 새 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 함수는 작업 ID를 가져와 목록에서 해당 항목을 삭제하고 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> );
앞에서 설명한 작업을 수행하는 데 필요한 단추를 추가했습니다. 단추의 아이콘으로 유니코드 문자를 사용하고 있습니다. 마크업에는 추후 CSS 추가를 지원하기 위해 추가된 몇 가지 속성이 있습니다. 선택 사항이지만 매우 권장되는 aria 특성을 사용하여 접근성을 개선할 수도 있습니다. 앱을 실행하는 경우 다음 그림과 같이 표시됩니다.
보여 주는 스크린샷
이제 TODO 웹앱에서 다음을 수행할 수 있습니다.
- 작업 추가
- 작업 삭제
- 작업 위로 이동
- 아래로 작업 이동
이러한 함수는 작동하지만 재사용 가능한 구성 요소를 빌드하도록 리팩터링하여 to-do 항목을 표시할 수 있습니다. to-do 항목에 대한 태그는 새 구성 요소인 TodoItem으로 이동합니다. 목록 관리는 Todo 구성 요소에 유지되므로 삭제 및 이동 단추에 콜백을 전달할 수 있습니다.
시작하려면 솔루션 탐색기에서 구성 요소 폴더를 마우스 오른쪽 단추로 클릭하고 > 새 항목추가를 선택합니다.
열리는 대화 상자에서 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 항목을 렌더링합니다. 해당 작업의 UUID 값을 포함하는 키가
task.id
으로 설정되어 있는 것을 확인하십시오. 앱을 실행할 때 TodoItem을 사용하도록 리팩터링했기 때문에 앱의 모양이나 동작에 대한 변경 내용이 표시되지 않아야 합니다.이제 모든 기본 함수를 지원했으므로 멋지게 보이도록 몇 가지 스타일을 추가해야 합니다. 먼저 이 앱에 사용할 글꼴 패밀리 Inter에 대한 Index.html 링크를 추가합니다. 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 구성 요소의 스타일을 업데이트합니다.
구성 요소 폴더에 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/IndexedDb저장하거나, 더 영구적인 스토리지를 위해 서버 쪽 데이터베이스 또는 기타 백 엔드와 통합할 수 있습니다.
요약
이 자습서에서는 Visual Studio를 사용하여 새 React 앱을 만들었습니다. 앱은 작업을 추가하고, 작업을 삭제하고, 순서를 다시 지정하는 지원을 포함하는 to-do 목록으로 구성됩니다. 두 개의 새 React 구성 요소를 만들고 이 자습서 전체에서 해당 구성 요소를 사용했습니다.
리소스
- ToDoJSWebApp 이 샘플에 대한 코드
- Visual Studio JavaScript 및 TypeScript 프로젝트