[Personal Project] Todo 리스트 만들기 (React)

liinyeye·2024년 5월 17일
0

Project

목록 보기
9/44

⚙️ features : 구현해야 할 기능

  • UI 구현하기
  • Todo 추가 하기
  • Todo 삭제 하기
  • Todo 완료/취소 상태 변경하기 (진행중 ↔ 완료)

🔆 완성된 모습


컴포넌트 구조

App > Layout > ToDoContainer > Header, ToDoForm > ToDoList > ToDoItem

const App = () => {
    return (
        <Layout>
            <ToDoContainer />
        </Layout>
    );
};
<div>
            <Header />
            <ToDoForm setLists={setLists} />
            <div>
                <ToDoList lists={workingLists} title="Working" setLists={setLists} />
                <ToDoList lists={doneLists} title="Done" setLists={setLists} />
            </div>
        </div>

ToDoContainer 컴포넌트 : 동일 props로 다른 값의 데이터 상태관리

Todo 완료/취소 버튼 상태 변경하는 부분이 필요

  • 두 개의 ToDoList 컴포넌트에 각각 다른 값의 데이터를 전달
  • 상단에 들어갈 title에도 다른 내용의 문자열을 넣어 props로 전달
<ToDoList lists={workingLists} title="Working" setLists={setLists} />
<ToDoList lists={doneLists} title="Done" setLists={setLists} />

isDone의 값에 따라 완료/취소 버튼의 상태가 정해지고, isDone이 false인 것만 담긴 배열 workingLists와 isDone이 true인 것만 담긴 배열 doneLists을 ToDoContainer 컴포넌트에서 만들어줘 자식 컴포넌트인 ToDoList에 넘겨준다. 또한, 제목으로 사용될 title도 마찬가지로 다른 문자열을 넣어 같은 title props로 전달해 자식 컴포넌트에서 사용할 수 있도록 한다.

const initialState = [
        { id: 0, title: "test1", content: "test1", isDone: false },
        { id: 1, title: "test1", content: "test1", isDone: true },
    ];

const [lists, setLists] = useState(initialState);

	// 다른 값의 데이터 전달해주기
    const workingLists = lists.filter((list) => !list.isDone); // isDone이 false인 것만 담긴 배열
    const doneLists = lists.filter((list) => list.isDone); // isDone이 true인 것만 담긴 배열

ToDoForm 컴포넌트

  • 비제어 컴포넌트로 form state관리
  • new formData()로 FormData 객체 활용
  • useState를 동기적으로 처리

제어 컴포넌트 & 비제어 컴포넌트란?

우선 React에 의해서 값이 제어되는 컴포넌트를 제어 컴포넌트, React에 의해서 값이 제어되지 않는 컴포넌트를 비제어 컴포넌트라고 한다.
form 이나 input 요소를 다룰 때, 요소에 입력되는 값을 state로 관리하거나 DOM API를 통해서 관리할 수 있는데, 이 때 state로 DOM element의 값을 다루는 컴포넌트가 제어 컴포넌트, 후자가 비제어 컴포넌트이다.

input 예시 코드

import React, { useState } from 'react';

function MyInput() {
  const [inputValue, setInputValue] = useState(null);

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  return <input onChange={(e) => handleChange(e)} value={inputValue} />;
}

위 코드는 input 의 값을 state로 관리하고 사용자가 값을 입력할 때마다 handleChange 를 통해 state 값을 업데이트 해주는 제어 컴포넌트이다. 제어 컴포넌트는 React, Real DOM, 사용자가 보는 화면을 동기화해줘야하는 번거로움이 있다.

비제어 컴포넌트는 state로 값을 관리하지 않기 때문에 값이 업데이트할 때마다 리렌더링이 되지 않기 때문에 성능상의 이점이 있다. 또한 빠르고 간편하게 적은 코드를 작성할 수 있다.

비제어 컴포넌트 new formData()로 FormData 객체 활용

// form 태그가 new ForData라는 새로운 API에 들어와서 forData로 값을 받음.
// 이 때 new formData() 안에는 html 폼이 들어가야 하기 때문에 event.target을 받음.
const formData = new FormData(event.target);
const title = formData.get("title");
const content = formData.get("content");

if (!title.trim() || !content.trim()) {
     return alert("제목과 내용을 입력해주세요.");
}

event.target.reset();
<input type="text" id="title" name="title" />
<input type="text" id="content" name="content" />

여기서 이벤트가 발생하는 event.target은 form객체이고, formData.get()메서드를 통해 input name에 지정된 값을 가져와 사용할 수 있다. 마지막에 event.target.reset()로 input에 적힌 값을 초기화시켜준다.

여기서 trim() 메서드는 문자열 양 끝의 공백을 제거하고 원본 문자열을 수정하지 않고 새로운 문자열을 반환해주며, 입력값에 공백이 있어도 submit되지 않도록 유효성 검사를 처리해준다.

useState를 동기적으로 처리

// 새롭게 만들어질 form 객체
const nextList = {
  id: uuid(),
  title,
  content,
  isDone: false,
};

// useState의 비동기성(잠재적인 오류의 위험)을 막고,
// 추가적으로 lists(props)를 가져오지 않기 위해,
// 익명콜백함수를 사용해서 기존의 값을 가져와서 넣어줌.
setLists((prev) => [nextList, ...prev]);

form이 submit되면 생성될 form 객체를 새롭게 만들어준다. 이후 삭제/취소에 활용하기 위해 고유한 id값을 부여해주고, 초기에는 모두 Working 상태로 두기 위해 isDone : false로 상태를 정해준다.

새로운 객체를 기존 배열에 넣어줄 때 setLists()안에 익명콜백함수를 넣어준다.
setLists([...lists, nextList]) 로 배열을 바로 수정해주는 방법도 있지만, useState의 비동기성으로 인한 잠재적인 오류의 위험을 막고 추가적으로 lists(props)를 가져오지 않기 위해 익명콜백함수를 넣어준다.


ToDoItem 컴포넌트 : 삭제 & 토글 버튼

  • filter(), map()메소드 활용한 불변성 유지
  • 구조 분해 할당
const ToDoItem = ({ list, setLists }) => {
    const { title, content, id, isDone } = list;

    const deleteButton = () => {
        // 선택한 id를 제외한 나머지만 배열만 새롭게 넣어줌.
        setLists((prev) => prev.filter((list) => list.id !== id));
    };
    const toggleButton = () => {
        //list.id와 내가 선택한 요소의 id가 일치하면 isDone을 반대로 바꿔줌
        setLists((prev) => prev.map((list) => (list.id === id ? { ...list, isDone: !list.isDone } : list)));
    };

    return (
        <li>
            <h2>{title}</h2>
            <p>{content}</p>
            <div>
                <button onClick={deleteButton}>Delete</button>
                <button onClick={toggleButton}>{isDone ? "Cancel" : "Complete"}</button>
            </div>
        </li>
    );
};

export default ToDoItem;

deleteButton 함수에서 filter() 메소드를 사용해 list.id !== id 로 선택한 id의 항목을 제외한 나머지 항목만 배열에 새롭게 넣어주고, toggleButton 함수에서는 map() 메소드를 이용해 배열을 돌면서 list.id === id 로 선택한 id의 항목과 같을 시 isDone 의 값을 반대로 바꿔서 배열에 다시 값으로 넣어 상태를 업데이트 시켜준다.

버튼에 적히는 내용도 마찬가지로 isDone 의 값에 따라 업데이트해주는데, 이 때 삼항연산자를 사용해 isDonetrue라면 "Cancel"을, false라면 "Complete"를 반환하도록 해준다.


참고 자료

profile
웹 프론트엔드 UXUI

0개의 댓글