todo :할일 앱,그리고 소스코드!

mgkim·2025년 1월 7일
2

react

목록 보기
35/36

할 일 수정 기능 구현

1 - 자바스크립트 공부하기
2 - 리액트 공부하기
3 - 할일 목록 앱 만들기

App 컴포넌트에 todos 상태변수에 값을 수정(checked 값 토글)하는 함수를 정의하고, 해당 함수를 TodoList 컴포넌트의 props로 전달

루프를 돌리던지 x아니예요


특정객체의 특정값을 바꾸는거예요.
배열의 개수는 그대로 유지되면서 특정값만 바꿀떄, 토글을 쓰는거예요
코드 이해하기!!

App 컴포넌트에 todos 상태변수에 값을 수정(checked 값 토글)하는 함수를 정의하고, 해당 함수를 TodoList 컴포넌트의 props로 전달

import { useRef, useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';

function App() {
  const [todos, setTodos] = useState([
    { id: 1, checked: true, text: "자바스크립트 공부하기" },
    { id: 2, checked: false, text: "리액트 공부하기" },
    { id: 3, checked: false, text: "할 일 목록 앱 만들기" },
  ]);

  const nextId = useRef(4);

  const insertTodo = text => {
    const newTodos = todos.concat({ id: nextId.current, checked: false, text });
    setTodos(newTodos);
    nextId.current++;
  };

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

  const toggleTodo = id => {
    /* 이건 루프로 돌린 코드얌.
    const newTodos = [];
    for (let i = 0; i < todos.length; i++) {
      const todo = todos[i];
      if (todo.id === id) {
        newTodos.push({ ...todo, checked: !todo.checked });
      } else {
        newTodos.push(todo);
      }
    }
    */
    const newTodos = todos.map(todo => todo.id === id ? { ...todo, checked: !todo.checked } : todo);
    setTodos(newTodos);
  };

  return (
    <TodoTemplate>
      <TodoInsert insertTodo={insertTodo} />
      <TodoList todos={todos} removeTodo={removeTodo} toggleTodo={toggleTodo} />
    </TodoTemplate>
  );
}

export default App;

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





소스코드 빌드

C:\react\todo-app>npm install http-server -g
글로벌하게 설치!!

C:\react\todo-app\build 디렉터리

2025-01-07 오전 10:42

.
2025-01-07 오전 10:39 ..
2025-01-07 오전 10:42 605 asset-manifest.json
2025-01-06 오후 04:16 3,870 favicon.ico
2025-01-07 오전 10:42 644 index.html
2025-01-06 오후 04:16 5,347 logo192.png
2025-01-06 오후 04:16 9,664 logo512.png
2025-01-06 오후 04:16 492 manifest.json
2025-01-06 오후 04:16 67 robots.txt
2025-01-07 오전 10:42 static
7개 파일 20,689 바이트
3개 디렉터리 433,076,158,464 바이트 남음

644 index.html 시작페이지처럼 동작한당

전체, 이파일들 호출, 빌드된 JS파일을 읽어들이는것,

https://www.npmjs.com/package/http-server

npx http-server ! 웹서버를 실행하는것, 웹다큐먼트루트 디렉토리로 설정해준다, 현재디렉터리를 .

Available on:
http://192.168.45.192:8080
http://127.0.0.1:8080
Hit CTRL-C to stop the server

8080서버로 이동 : 로컬호스트는 웹서버 엔드포인트!!! 이서버뒤에는
웹 다큐먼트루트를 기본페이지를 읽어들이는것이야!!

브라우저로 실행 :
보여주는 이유는? 내가만든 어플리케이션 배포시!
웹서버가 있으면, 홈디렉토리에 npm build 한 결과물을 갖다놔야해요

  <TodoTemplate>
      <TodoInsert insertTodo={insertTodo} />
      <TodoList todos={todos} removeTodo={removeTodo} toggleTodo={toggleTodo} />
    </TodoTemplate>

컨텍스트 API와 유즈컨텍스트 훅을 이용해서 insertTodo, removeTdo, toggleTodo함수와 tods변수를 props 변수로 전달하지 않고 사용하게 수정해보자!!!

아래표는 스터디내용을 정리:

기능구현된 위치설명
할 일 추가App, TodoInsertApp 컴포넌트에서 todos 상태변수에 새로운 항목을 추가하는 insertTodo 함수를 정의하고, 이를 TodoInsert 컴포넌트에 props로 전달. TodoInsert에서 버튼 클릭 시 insertTodo 호출.
할 일 삭제App, TodoList, TodoListItemApp 컴포넌트에서 todos 상태변수에서 항목을 삭제하는 removeTodo 함수를 정의하고, 이를 TodoList 컴포넌트에 전달. TodoList에서 removeTodoTodoListItem으로 전달 후 삭제 버튼 클릭 시 호출.
할 일 수정App, TodoList, TodoListItemApp 컴포넌트에서 todos 상태변수의 checked 값을 토글하는 toggleTodo 함수를 정의하고, 이를 TodoList 컴포넌트에 전달. TodoList에서 toggleTodoTodoListItem으로 전달 후 체크박스 클릭 시 호출.


문제풀기

1 Context API와 useContext 훅을 이용해서
2 insertTodo, removeTodo, togggleTodo 함수와
3 todos 변수를 props 변수로 전달하지 않고 사용할 수 있도록 수정 하는 코드

Context APIuseContext 훅을 사용하면 props를 통해 반복적으로 데이터를 전달하지 않고도 컴포넌트 간 상태를 공유할 수 있습니다. 아래는 Context APIuseContext를 사용하여 insertTodo, removeTodo, toggleTodo 함수와 todos 변수를 관리하는 수정된 코드입니다.

1. Context 생성

TodoContext를 생성하여 todos 상태와 함수들을 관리합니다.

import React, { createContext, useContext, useState, useRef } from "react";

// Context 생성
const TodoContext = createContext();

// Context Provider 컴포넌트
export const TodoProvider = ({ children }) => {
  const [todos, setTodos] = useState([
    { id: 1, checked: true, text: "자바스크립트 공부하기" },
    { id: 2, checked: false, text: "리액트 공부하기" },
    { id: 3, checked: false, text: "할 일 목록 앱 만들기" },
  ]);

  const nextId = useRef(4);

  const insertTodo = (text) => {
    const newTodos = todos.concat({ id: nextId.current, checked: false, text });
    setTodos(newTodos);
    nextId.current++;
  };

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

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

  return (
    <TodoContext.Provider value={{ todos, insertTodo, removeTodo, toggleTodo }}>
      {children}
    </TodoContext.Provider>
  );
};

// Context Consumer 훅
export const useTodos = () => useContext(TodoContext);

2. App 컴포넌트

TodoProvider로 앱 전체를 감쌉니다.

import React from "react";
import { TodoProvider } from "./TodoContext";
import TodoTemplate from "./components/TodoTemplate";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";

function App() {
  return (
    <TodoProvider>
      <TodoTemplate>
        <TodoInsert />
        <TodoList />
      </TodoTemplate>
    </TodoProvider>
  );
}

export default App;

3. TodoInsert 컴포넌트

useTodos 훅으로 insertTodo를 호출합니다.

import React, { useState } from "react";
import { useTodos } from "../TodoContext";
import "./TodoInsert.css";

export default function TodoInsert() {
  const [value, setValue] = useState("");
  const { insertTodo } = useTodos();

  const onSubmit = (e) => {
    e.preventDefault();
    if (value.trim() === "") return;
    insertTodo(value);
    setValue("");
  };

  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="할 일을 입력하세요"
      />
      <button type="submit">추가</button>
    </form>
  );
}

4. TodoList 컴포넌트

useTodos 훅으로 todos를 가져옵니다.

import React from "react";
import { useTodos } from "../TodoContext";
import TodoListItem from "./TodoListItem";
import "./TodoList.css";

export default function TodoList() {
  const { todos } = useTodos();

  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

5. TodoListItem 컴포넌트

useTodos 훅으로 removeTodotoggleTodo를 가져옵니다.

import React from "react";
import { MdCheckBox, MdCheckBoxOutlineBlank, MdRemoveCircleOutline } from "react-icons/md";
import { useTodos } from "../TodoContext";
import "./TodoListItem.css";

export default function TodoListItem({ todo }) {
  const { id, checked, text } = todo;
  const { removeTodo, toggleTodo } = useTodos();

  return (
    <div className="TodoListItem">
      <div
        className={checked ? "checkBox checked" : "checkBox"}
        onClick={() => toggleTodo(id)}
      >
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => removeTodo(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
}

이제 todos 상태와 관련된 함수들을 props로 전달하지 않고,
Context API를 통해 컴포넌트 전반에서 쉽게 사용할 수 있어!

  src
├── App.js
├── TodoContext.js
├── components
│   ├── TodoInsert.js
│   ├── TodoInsert.css
│   ├── TodoList.js
│   ├── TodoList.css
│   ├── TodoListItem.js
│   ├── TodoListItem.css
│   ├── TodoTemplate.js
│   └── TodoTemplate.css
└── index.js

프로젝트 생성
c:\react\my-cra-app> cd c:\react
c:\react> npx create-react-app todo-app
c:\react> cd todo-app
c:\react\todo-app> npm install react@18 react-dom@18
c:\react\todo-app> npm install web-vitals
c:\react\todo-app> npm install react-icons classnames
c:\react\todo-app> code .
c:\react\todo-app> npm start

다시해봤어...ㅠㅠ

물론입니다! 말씀하신 순서에 따라 코드를 작성하겠습니다.


1. 컨텍스트 생성 ⇒ TodoContext.js

src/TodoContext.js 파일을 생성하고 아래 코드를 작성하세요:

// src/TodoContext.js
import { createContext } from "react";

// TodoContext 생성
const TodoContext = createContext();

export default TodoContext;

2. 프로바이더 생성 ⇒ TodoProvider.js

src/TodoProvider.js 파일을 생성하고 아래 코드를 작성하세요:

// src/TodoProvider.js
import React, { useState, useRef } from "react";
import TodoContext from "./TodoContext";

export default function TodoProvider({ children }) {
  const [todos, setTodos] = useState([
    { id: 1, checked: true, text: "자바스크립트 공부하기" },
    { id: 2, checked: false, text: "리액트 공부하기" },
    { id: 3, checked: false, text: "할 일 목록 앱 만들기" },
  ]);

  const nextId = useRef(4);

  const insertTodo = (text) => {
    const newTodos = todos.concat({ id: nextId.current, checked: false, text });
    setTodos(newTodos);
    nextId.current++;
  };

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

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

  return (
    <TodoContext.Provider value={{ todos, insertTodo, removeTodo, toggleTodo }}>
      {children}
    </TodoContext.Provider>
  );
}

3. 프로바이더 설정 ⇒ App.js

App.js 파일을 수정하여 TodoProvider로 앱을 감싸줍니다.

// src/App.js
import React from "react";
import TodoProvider from "./TodoProvider";
import TodoTemplate from "./components/TodoTemplate";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";

function App() {
  return (
    <TodoProvider>
      <TodoTemplate>
        <TodoInsert />
        <TodoList />
      </TodoTemplate>
    </TodoProvider>
  );
}

export default App;

4. 컨텍스트 변수를 사용

(1) TodoInsert.js에서 useContextinsertTodo 사용

// src/components/TodoInsert.js
import React, { useState, useContext } from "react";
import TodoContext from "../TodoContext";
import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";

export default function TodoInsert() {
  const [value, setValue] = useState("");
  const { insertTodo } = useContext(TodoContext);

  const onSubmit = (e) => {
    e.preventDefault();
    if (value.trim() === "") return;
    insertTodo(value);
    setValue("");
  };

  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="할 일을 입력하세요"
      />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
}

(2) TodoList.js에서 useContexttodos 사용

// src/components/TodoList.js
import React, { useContext } from "react";
import TodoContext from "../TodoContext";
import TodoListItem from "./TodoListItem";
import "./TodoList.css";

export default function TodoList() {
  const { todos } = useContext(TodoContext);

  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

(3) TodoListItem.js에서 useContextremoveTodo, toggleTodo 사용

// src/components/TodoListItem.js
import React, { useContext } from "react";
import TodoContext from "../TodoContext";
import {
  MdCheckBox,
  MdCheckBoxOutlineBlank,
  MdRemoveCircleOutline,
} from "react-icons/md";
import "./TodoListItem.css";

export default function TodoListItem({ todo }) {
  const { id, checked, text } = todo;
  const { removeTodo, toggleTodo } = useContext(TodoContext);

  return (
    <div className="TodoListItem">
      <div
        className={checked ? "checkBox checked" : "checkBox"}
        onClick={() => toggleTodo(id)}
      >
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => removeTodo(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
}

최종 파일 구조

src
├── App.js
├── TodoContext.js
├── TodoProvider.js
├── components
│   ├── TodoInsert.js
│   ├── TodoInsert.css
│   ├── TodoList.js
│   ├── TodoList.css
│   ├── TodoListItem.js
│   ├── TodoListItem.css
│   ├── TodoTemplate.js
│   └── TodoTemplate.css

요약

  • TodoContext.js: 컨텍스트를 생성.
  • TodoProvider.js: TodoContext.Provider를 구현하여 상태와 함수를 제공.
  • App.js: TodoProvider로 앱 감싸기.
  • 컴포넌트: useContext로 필요한 상태 및 함수 사용.
profile
@lala.love_garden.lala

0개의 댓글