Toy Project - ToDo_List(React)

황영훈·2024년 5월 9일

TOY_PROJECT

목록 보기
3/3
post-thumbnail

1. Intro

리액트를 공부하고서 리액트에 익숙해지고, 개념들을 정리하기 위해 만든 간단한 토이프로젝트이기에 라이브러리는 사용하지 않고 개발했다.

💻 개발 환경

  • 프로젝트 기능 : 할 일 관리 WEB
  • Editor : Visual Studio Code
  • Tech Stack : React, CSS Module, useContext, JSON Server
  • 배포 : Github.io(gh-pages), Glitch
  • Link : https://0huns.github.io/todo

참고로 이 토이프로젝트는 page가 하나라서 props로만 데이터를 전달해도 충분하지만 이후에 Redux, Recoil과 같은 라이브러리를 사용하기 위해 useContext로 관리해봤다.

2. UI / UX

‣ 🌟반응형 웹

CSS Module로 스타일을 관리하였다. 미디어쿼리를 활용하여 간단하게 반응형 웹으로 구성하였으며, fontSizerem을 사용하여 화면이 줄어들어도 전체적인 프레임이 유지되도록 구현하였다.

‣ 📌스타일 컨셉

boxShadow를 사용하여 블럭 느낌으로 디자인하였고, 각 버튼에는 가상 선택자 :active를 이용해 버튼이 눌리는 듯한 효과를 구현했다.

3. 주요 기능

‣ 💡 List 생성 / 삭제

LIST 생성(추가)를 위한 Input 요소와 state를 이용해 입력값을 저장한 후, Form > button 요소의 핸들러를 활용하여 입력 데이터를 기존 List에 추가하였다. DB는 따로 연동하지 않았기에 List 데이터는 state에 배열로 저장하여 사용하였다.

저장된 List state를 map() 메소드를 통해 ListFrame에 뿌려주었으며, 이때 map()의 인자 index를 통해 key를 설정(Trouble)했다.

map() 메소드로 List를 콜백할 때, 삭제 버튼을 포함하여 각각의 List 우측에 배치하였다. 삭제 버튼의 onClick Event에 key값인 List index를 인자로 주어 클릭 시 해당하는 Key와 List 배열의 index가 일치하는 값을 filter() 메서드를 통해 재반환하는 형식으로 삭제 기능을 구현했다. 이때 삭제 기능은 filter()를 통해 배열을 순환하기에 배열의 길이가 길어질수록 성능이 저하될 수 있기에 useCallback Hook을 이용하였다.

function TodoListFrame() {
  const {todoList, setTodoList} = useContext(ListSetContext);

  const onDelete = useCallback((id)=>{
    setTodoList((prev)=>prev.filter((_, item)=>item !== id))  
  },[]);

  return (
    <div className={todoListFrame.listFrame}>
      {todoList.map((item, index)=>{
        return(
          <li key={index} className={todoListFrame.listItem}>
            <input type='checkbox' className={todoListFrame.checkBox}/>
            <TodoListUpdate item={item}/>
            <button className={todoListFrame.delBtn} onClick={()=> onDelete(index)}></button>
          </li>
        )
      })}
    </div>
  );
}

( 삭제를 통해 배열의 순서가 변경되지만, filter()를 통해 반환된 값으로 상태가 변경되어 리렌더링과 함께 다시 map()을 불러오기에 Key 값을 index로 설정해도 map index와 배열 index가 동일하여 문제가 생기지 않았다. 이때까지는,,, )

‣ 💡 List 완료 체크

각각의 List에 대한 완료 표시를 위해 List와 함께 Input CheckBox를 뿌려주면 개별적인 체크 표시가 될 것이라 생각했다.
결론적으로 반은 맞았지만 반은 틀렸다.
List 각각의 체크 표시가 되었지만 List를 추가/수정을 하게 되면 체크된 항목을 기억하지 못하였다. 이는 List의 key값을 index로 주어 고유한 Key값을 갖지 못해 생기는 문제였다.
고유한 id를 부여하지 않고도 체크 표시를 관리할 수 있지만 이런 경우 배열이 변경될 때마다 모든 항목이 리렌더링되어 비효율적이다.
따라서 List에 고유 id를 주어 관리하였다. 이때 안정성을 위해 uuid사용을 고민했지만 아직까지는 다중 사용자 환경이 아니기에 필요성을 느끼지 못했다. 오히려 추후에 메모 날짜 등을 표기에 용이하고 단일 사용자 환경에서는 중복될 가능성도 적을 것 같아 Date()를 사용하여 id를 부여하였다.

// list 등록 
  const onFormSubmit = (e)=>{
    e.preventDefault();
    if(input.trim() === ""){
      alert("내용을 입력하세요.");
    } else {
      const newItem = {
        id: Date.now(),
        text: input,
        check: false
      };
      setTodoList((prev)=>[newItem, ...prev]);
      setInput('');
      inputFocus.current.focus();
    }
  }

// list 삭제 / 체크 시
  const onDelete = useCallback((id)=>{
    setTodoList((prev)=>prev.filter((item)=>item.id !== id))  
  },[]);

  return (
    <div className={todoListFrame.listFrame}>
      {todoList.map((item)=>{
        return(
          <li key={item.id} className={todoListFrame.listItem}>
            <input type='checkbox' className={todoListFrame.checkBox}/>
            <TodoListUpdate item={item}/>
            <button className={todoListFrame.delBtn} onClick={()=> onDelete(item.id)}></button>
          </li>
        )
      })}
    </div>
  );

‣ 💡 List 수정

List 수정은 삭제와 비슷하게 onClick Event에 List id를 전달하여 일치하는지 확인 후 state조건문으로 list text 부분을 input text로 변경해주었다. input text에 value를 기존 text로 설정하고 useRef Hook으로 input에 focus를 맞춰 수정에 용이하게 하였다. 추가적으로 수정 시 사용한 state를 이용하여 취소 버튼을 제공해 사용성을 높였다.

‣ 🚩 (추가) 데이터 저장 및 통신

기존의 TODO 프로젝트는 새로고침 시 데이터가 유지되지 않고 default 값을 다시 보여주었는데, 재시작이나 새로고침을 해도 데이터가 유지될 수 있게 DB에 정보를 저장하여 서버와 통신을 해보고 싶어 추가 수정하였다. 원래 express와 MongoDB를 활용하여 직접 서버 및 DB를 생성해보려 했지만 다중사용자 환경이 아니기도 하고 데이터 통신에 익숙해져보기 위해 JSON Server를 사용하여 구현했다.
npm을 사용하여 json-server 설치 후 db.json을 생성하여 초기 데이터를 작성하였다. 그 후 json-server --watch db.json --port xxxx를 사용하여 실행해주었다. 규모가 작은 토이 프로젝트이기에 AXIOS가 아닌 Fetch API를 사용하여 데이터 통신을 하였으며, http 메서드 중 GET,POST를 통해 데이터 가져오기 및 전송을 하였다. 수정 부분을 위해 PUT 메서드를 사용하려했지만 이 프로젝트에서는 완료 표시 또는 아이템의 text 만 부분적으로 수정하기에 PATCH메서드를 사용하였다.
fetch는 HTTP error 상태를 반환하지 않기에 response.okcatch를 통해 에러처리를 했다.
배포까지 해보고 싶어 찾아봤더니 github는 정적 사이트 호스팅 서비스이고 json-server는 Node.js 기반으로 작동하는 동적 서버이기에 나는 기존 배포를 위해 사용한 gh-pages와 함께 Glitch를 사용하여 간단하게 배포해봤다.

//GET 예시
useEffect(() => {
    fetch('https://electric-good-hippodraco.glitch.me/todoList')
      .then(res => {
        if (!res.ok) {
          throw new Error('서버 연결을 실패하였습니다.');
        }
        return res.json();
      })
      .then(data => setTodoList(data))
      .catch(error => {
        alert(error.message);
      });
  }, []);

🎉마무리

막연하게 이론만 알고 있던 리액트를 활용해서 토이프로젝트를 간단하게 개발해봤는데 여러가지 고려해야될 부분들이 생각보다 많아서 어려웠지만 훨씬 흥미로운것 같다. 하지만 컴포넌트화 시켜서 조금 더 심플한 구조를 만들 수 있었겠다는 아쉬움이 남는다. 또한 배포를 하는데 있어서 필요한 파일을 끌어와 사용해봤는데 맛만 본 느낌이라 조금 더 구체적으로 파고들어 공부할 필요성을 느꼈다.
그리고 체크 표시를 할 때 지연이 발생하는 느낌이 들어 개발자 도구로 네트워크 검사를 실행했는데 체크 표시를 png로 활용하였더니 버벅거림이 생기는 거 같다. 이 부분을 유념해서 최적화에 고려해야겠다는 생각이 든다.
다음 프로젝트에서는 Route를 사용한 페이지 관리와 추가적인 라이브러리,서버 통신,배포 등을 활용해서 개발해봐야겠다.

profile
Faithfulness makes all things possible.

0개의 댓글