카테고리 없음

새싹 성동 2기 리액트 part5

news6001 2024. 11. 17. 19:23

생성형 AI 활용한 클라우드&보안 전문가 양성 캠프

 

TODO 일정 관리 앱 만들어보기 feat 리액트

 

클론 코딩느낌으로 하나씩 따라 가면서 만들어 보겠습니다.

 

TODO. 일정 관리 앱

 

우선 프로젝트를 생성합니다.

 

명령 프롬포트를 켜서 경로를 잡아 줍니다.

 

저는 c:\javaScript 디렉토리에 todo-app 생성 하고자 합니다.

 

생성 할때의 명령어는 npx create-react-app todo-app 

 

c:\javaScript> npx create-react-app todo-app

이렇게 작성한 후 설치를 기다립니다!!!!

 

설치가 완료되면 "Happy Hacking" 이라는  문구가 나옵니다.

 

이제 CRA(create-react-app)으로 생성한 파일을 프로젝트에 맞도록 초기화 하겠습니다.

 

우선 index.css 을 수정해줍니다.

 

index.css

body {
  margin: 0;
  padding: 0;
  background: #e9ecef;
}

 

초기화 내용으로는 모든 엘리먼트의 바깥 여백을 없애고 모든 엘리먼트의 안쪽 여백도 없애고 연한 회색으로 페이지

배경색을 설정합니다.

 

이제 app.js를 수정 해봅시다.

 

App.js

import './App.css';

function App() {
  return (
    <div>TODO App</div>
  );
}

export default App;

 

컴포넌트 디렉토리 만들어주고 기능을 하나씩 구현해봅니다.

 

디렉토리 이름은 components 로 만들어 주고 js 파일을 만들어 줍니다.

 

TodoTemplate.js

import "./TodoTemplate.css";

const TodoTemplate = (props) => {
    return (
        <>
            <div className="container">
                <div className="title">일정관리</div>
                <div className="content">{props.children}</div>
            </div>
        </>
    );
};

export default TodoTemplate;

 

부모 컴포넌트에서 전달한 내용을 props.children을 통해 여기에 렌더링 한다

이러면 재사용 가능한 컴포넌트가 된다.

 

이제 TodoInsert 컴포넌트 관련 파일을 생성해보자

Material Design Icons

https://react-icons.github.io/react-icons/icons/md/


우선 icon을 사용하기 위해 명령 프롬포트에 패키지를 설치한다.

 

c:\javascript\todo-app> npm install react-icons 

 

이제 입력하는  js를 만들어주자

 

TodoInsert.js

import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";

export default function TodoInsert(props) {
    return (
        <>
            <form className="insert">
                <input type="text" placeholder="할일을 입력하세요."></input>
                <button type="submit"><MdAdd/></button>
            </form>
        </>
    );
};

react-icons 라이브러리에서 MdAdd(+)를 불러서 사용했다.

 

 

TodoListItem 컴포넌트 관련 파일을 생성합니다.

TodoListItem.js

import { MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";

export default function TodoListItem() {
    return (
        <>
            <div className="item">
                <div className="checkbox">
                    <MdCheckBoxOutlineBlank />
                    <div className="text">할 일</div>
                </div>
                <div className="remove">
                    <MdOutlineRemoveCircleOutline />
                </div>
            </div>
        </>
    );
};

 

TodoList 컴포넌트 관련 파일을 생성합니다.

 

TodoList.js

import TodoListItem from "./TodoListItem";
import "./TodoList.css";

export default function TodoList() {
    return (
        <>
            <div className="list">
                <TodoListItem />
                <TodoListItem />
                <TodoListItem />
            </div>
        </>
    );
};

 

할 일 목록 기능 구현

App 컴포넌트에 할 일 목록 데이터를 관리하는 상태변수를 정의하고, TodoList 컴포넌트에 props 변수로 전달

App.js

import { 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: '할 일 목록 앱 만들기' }, 
  ]);

  return (
    <div>
      <TodoTemplate>
        <TodoInsert />
        <TodoList todos={todos} />
      </TodoTemplate>
    </div>
  );
}

export default App;

 

TodoList 컴포넌트에서 props 변수로 전달된 배열 값을 이용해서 TodoListItem 컴포넌트를 추가

TodoList.js

import TodoListItem from "./TodoListItem";
import "./TodoList.css";

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

 

 

TodoListItem 컴포넌트에서 props 변수로 전달된 할 일 내용을 출력

TodoListItem.js

import { MdCheckBox, MdCheckBoxOutLineBlank, MdOutlineRemoveCircleOutLine } from "react-icons/md";
import "./TodoListItem.css";

export default function TodoListItem({todo}) {
	console.log(todo);
    //객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
    const {id, checked, text} = todo;
    return (
    	<> 
        	<div className="item">
            	<div className={checked ? "checkbox checked" : "checkbox"}>
                     {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                     <div className="text">{text}</div>
                </div>
                <div className ="remove">
                	<MdOutlineRemoveCircleOutline />
                </div>
              </div>
           </>
      );
};

 

할 일 추가 기능 구현

TodoInsert 컴포넌트에 입력 기능을 구현

import { MdAdd } from "react-icons/md"
import "./TodoInsert.css";
import { useState } from "react";

export default function TodoInsert(props){
	const [todo, setTodo] = useState('');
    const changeTodo = e => setTodo(e.target.value);
    return (
    	<>
         	<from className ="insert">
            	<input type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
                <button type ="submit"><MdAdd/></button>
            </from>
        </>
    );
};

 

App 컴포넌트에 todos 상태변수에 값을 추가하는 함수를 정의하고, 해당 함수를 TodoInsert 컴포넌트의 props 변수로 전달 => 할 일이 추가될 때 사용할 id값을 관리하는 변수를 정의 <= useRef를 이용해서 생성

 

App.js

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 ++;
  };
  return (
    <div>
      <TodoTemplate>
        <TodoInsert />
        <TodoList todos={todos} />
      </TodoTemplate>
    </div>
  );
}

export default App;

 

TodoInsert 컴포넌트에 추가 버튼을 클릭했을 때 동작을 추가

 

TodoInsert.js

import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";
import { useState, useRef } from "react";

export default function TodoInsert({insertTodo}) {
    const [todo, setTodo] = useState('');
    const changeTodo = e => setTodo(e.target.value);

    // 할 일 입력창에 포커스 부여에 사용할 ref 변수를 생성 
    const refInput = useRef();

    const handleSubmit = e => {
        // submit 이벤트가 발생했을 때 브라우저의 기본 동작이 발생하지 않도록 처리
        e.preventDefault();
        insertTodo(todo);
        setTodo('');
        refInput.current.focus();
    };
    return (
        <>
            <form className="insert" onSubmit={handleSubmit}>
                <input ref={refInput} type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
                <button type="submit"><MdAdd/></button>
            </form>
        </>
    );
};

 

할 일 삭제 기능 구현

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

App.js

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);
  };
  return (
    <div>
      <TodoTemplate>
        <TodoInsert insertTodo={insertTodo} />
        <TodoList todos={todos} removeTodo={removeTodo} />
      </TodoTemplate>
    </div>
  );
}

export default App;

 

TodoList 컴포넌트에서 props 변수로 전달받은 removeTodo 함수를 TodoListItem 컴포넌트의 props 변수로 전달 

TodoList.js

import TodoListItem from "./TodoListItem";
import "./TodoList.css";

export default function TodoList({todos, removeTodo}) {
    return (
        <>
            <div className="list">
                { todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} />) }
            </div>
        </>
    );
};

 

TodoListItem 컴포넌트에서 삭제 버튼을 클릭하면 props 변수로 전달받은 removeTodo 함수를 호출

TodoListItem.js

import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";

export default function TodoListItem({todo, removeTodo}) {
    console.log(todo);
    // 객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
    const {id, checked, text} = todo;
    return (
        <>
            <div className="item">
                <div className={checked ? "checkbox checked" : "checkbox"}>
                    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                    <div className="text">{text}</div>
                </div>
                <div className="remove" onClick={() => removeTodo(id)}>
                    <MdOutlineRemoveCircleOutline />
                </div>
            </div>
        </>
    );
};

 

할 일 수정 기능 구현 => 할 일 체크 상태를 변경

 

App 컴포넌트에서 todos 상태변수에 값을 수정(cheked 속성의 값을 토글)하는 함수를 정의하고, 해당 함수를 TodoList

컴포넌트의 props 변수로 전달

App.js

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 toggleCheck = id => {
    const newTodos = todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo)
    setTodos(newTodos);
  };
  return (
    <div>
      <TodoTemplate>
        <TodoInsert insertTodo={insertTodo} />
        <TodoList todos={todos} removeTodo={removeTodo} toggleCheck={toggleCheck} />
      </TodoTemplate>
    </div>
  );
}

export default App;

 

TodoList 컴포넌트에서 props 변수로 전달받은 toggleCheck 함수를 TodoListItem 컴포넌트의 props 변수로 전달

TodoList.js

import TodoListItem from "./TodoListItem";
import "./TodoList.css";

export default function TodoList({todos, removeTodo, toggleCheck}) {
    return (
        <>
            <div className="list">
                { todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} toggleCheck={toggleCheck} />) }
            </div>
        </>
    );
};

 

TodoListItem 컴포넌트에서 체크박스를 클릭하면 props 변수로 전달받은 togglecheck 함수를 호출

TodoListItem.js

import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";

export default function TodoListItem({todo, removeTodo, toggleCheck}) {
    console.log(todo);
    // 객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
    const {id, checked, text} = todo;
    return (
        <>
            <div className="item">
                <div className={checked ? "checkbox checked" : "checkbox"} onClick={() => toggleCheck(id)}>
                    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                    <div className="text">{text}</div>
                </div>
                <div className="remove" onClick={() => removeTodo(id)}>
                    <MdOutlineRemoveCircleOutline />
                </div>
            </div>
        </>
    );
};

 

Context API와 useContext 훅을 이용해서 insertTodo, removeTodo,toggleCheck 함수를 props 변수로 전달하지 않고 사용할 수 있도록 수정

TodoContext.js

import { createContext } from "react";

const TodoContext = createContext();

export default TodoContext;

 

TodoProvider.js

import { useRef, useState } from "react";
import TodoContext from "./TodoContext";

export default function TodoProvider(props) {
    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 toggleCheck = id => {
        const newTodos = todos.map(todo => todo.id === id ? { ...todo, checked: !todo.checked } : todo)
        setTodos(newTodos);
    };

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

 

 

app,js => TodoProvicer 컴포넌트로 감싸고, 컨텍스트를 통해서 제공받을 수 있는 상태변수, 함수, props 변수를 삭제

 

APP.js

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

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

export default App;

TodoInsert.js

import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";
import { useState, useRef, useContext } from "react";
import TodoContext from "./TodoContext";

export default function TodoInsert() {
    const {insertTodo} = useContext(TodoContext);

    const [todo, setTodo] = useState('');
    const changeTodo = e => setTodo(e.target.value);

    // 할 일 입력창에 포커스 부여에 사용할 ref 변수를 생성 
    const refInput = useRef();

    const handleSubmit = e => {
        // submit 이벤트가 발생했을 때 브라우저의 기본 동작이 발생하지 않도록 처리
        e.preventDefault();
        insertTodo(todo);
        setTodo('');
        refInput.current.focus();
    };
    return (
        <>
            <form className="insert" onSubmit={handleSubmit}>
                <input ref={refInput} type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
                <button type="submit"><MdAdd/></button>
            </form>
        </>
    );
};

 

TodoList.js

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

export default function TodoList() {
    const {todos} = useContext(TodoContext);
    return (
        <>
            <div className="list">
                { 
                    // todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} toggleCheck={toggleCheck} />) 
                    todos.map(todo => <TodoListItem key={todo.id} todo={todo} />)    
                }
            </div>
        </>
    );
};

TodoListItem.js

import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";
import { useContext } from "react";
import TodoContext from "./TodoContext";

export default function TodoListItem({todo}) {
    const {removeTodo, toggleCheck} = useContext(TodoContext);
    
    const {id, checked, text} = todo;
    return (
        <>
            <div className="item">
                <div className={checked ? "checkbox checked" : "checkbox"} onClick={() => toggleCheck(id)}>
                    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                    <div className="text">{text}</div>
                </div>
                <div className="remove" onClick={() => removeTodo(id)}>
                    <MdOutlineRemoveCircleOutline />
                </div>
            </div>
        </>
    );
};