TIL 231108 - To-do List 회고

송용승·2023년 11월 8일
2

TIL

목록 보기
12/29
post-thumbnail

Today I Learned

며칠 전 React 입문주차 첫번째 과제가 마무리되었다. 어려웠던 점도 있었고, 뿌듯했던 점도 있었다. 과제를 전체적으로 돌아보자.

useState... 또 너야?

유일하게 배웠고 쓸 수 있는 hook이 useState 뿐인데... 이놈이 이번에도 많이 괴롭혔다.

  1. state 생성
  2. state 참조 및 setState 사용
  3. 문제점
  4. 해결 방법

이 정도로 다뤄보자.

state 생성

강의를 들을 때엔 막힘없이 술술 진행할 수 있었는데 과제로 주어지고 보니 state가 뭐고 useState는 어디에 쓰는거고... 역시 코드는 실천이다. 써 보기 전까지는 아는 게 아니다...

제대로 생각을 해보자. 이거저거 다 state를 쓸 수는 없다. 어떤 side effect가 있을지는 모르지만 일단

state 가 많아지면 → 리렌더링 횟수가 늘어나고 → 부하가 커질 것이다

라고 처음엔 생각했지만, 실제로 리렌더링 횟수가 좀 늘어난다고 퍼포먼스에 유의미한 영향을 미치진 않는 것으로 보인다. 정확한 데이터는 없지만... 문제가 되는 부분은 말 그대로 무한 렌더링이 일어나 페이지가 제대로 로드될 수 없는 정도겠지. 그러므로 state 를 많이 만든다고 해서 기능상의 큰 문제는 없을 것이다. 하지만 일단 덮어놓고 state 로 선언하는 일만은 피해야 할 것이다. 물론

  1. 이제 막 배우기 시작한 내 입장을 생각하면 클린한 코드보다는 기능 구현이 우선이다.
  2. 프로젝트를 만들어 본 경험도 적은데 필요한 state 를 제대로 예상할 수 있지는 않을 것이다.
  3. 나는 일단 뛰어들어 볼 줄도 알아야한다. 실수를 겪어봐야 성장하는 법!

같은 생각도 들었지만 시간 여유가 있는 만큼 조건이라도 설정하기로 한다. 이런 저런 생각을 거쳐 뽑아낸 조건은 다음과 같다.

  • 기능 또는 프로젝트의 목표를 달성하는 데 중요한 정보여야한다. 당연한 이야기다.
  • 계속 추가, 변경 또는 삭제되는 정보여야한다.
  • 다른 컴포넌트 및 함수에서 계속해서 참조하는 정보여야 한다.
function Todolist() {
  const [todoItem, setTodoItem] = useState({});
  const [todoList, setTodoList] = useState([]);

위의 세가지를 고려해서 처음 구상했던 state 의 목록은 todoItemtodoList 의 두 개였다. 필요에 따라 더 추가할 수 있겠지만 우선 이 상태들을 가지고 앱을 만들어나갔다.

state 참조, setState 사용

to-do list의 가장 핵심이 되는 기능은 제목과 내용을 입력하고 submit 버튼을 누르면 할 일을 등록하는 것이다. 그렇기에 기본적인 컴포넌트 분리로 앱의 골격만 잡고 이 역할을 해 줄 함수 handleSubmitBtn 을 만들었다.

  const handleSubmitBtn = (e) => {
    e.preventDefault();
    const title = document.querySelector("#title");
    const content = document.querySelector("#content");
    if (title.value.trim() && content.value.trim()) {
      const item = {
        id: Date.now(),
        title: title.value,
        content: content.value,
        isDone: false,
      };
      setTodoItem({...item});
      setTodoList([todoItem, ...todoList]);
    }
  };

그러나...................................

useState 는 비동기적으로 처리된다?

local 변수 item 객체를 만들고 trim 한 값을 할당한 뒤 state 객체로 설정하고 그 todoItemsetTodoList 의 파라미터로 전달한다. todoList state 의 맨 앞에 넣고 기존의 원소를 spread 해서 새로운 배열을 todoList 로 설정하는 기초적인 코드이다. 그런데 이렇게 아이템을 넣고 생성되는 할 일 항목들을 보니 문제가 있었다. 두 개의 아이템을 넣었는데 넣은 적 없는 빈 아이템이 있는 것이다.

한참을 여기에 막혀있었다. todoItem 에 대한 상태를 만들 때 초기값을 빈 객체로 주었는데, 어찌된 이유에선지 setTodoItem(item) 으로 내용을 채운 객체로 업데이트했는데도 매번 첫 번째 아이템이 빈 객체로 들어갔다... 여기서 시간을 얼마나 썼는지.... 여기저기 찾아도 봤지만 별다른 소득이 없었고, ChatGPT에게 물어보자 알쏭달쏭한 답변을 내놨다.

React에서 useState 와 상태 업데이트는 비동기적으로 처리된다.

흠... 나는 다양한 경험을 통해 ChatGPT를 너무 믿으면 안 된다는 것을 배웠다. 그래도 얘가 없는 이야기는 잘 안 하니까... 맞는 말을 했다고 가정한다면

  • 현재 handleSubmitBtn 함수에서는 두 개의 서로 얽혀있는, 다시말해 todoList가 상태 업데이트 함수 setTodoList 에서 다른 상태 todoItem 을 참조하는 식으로 상태가 업데이트 되고 있다.
  • 상태 업데이트가 비동기적으로 처리된다면 todoList 를 업데이트 할 때 todoItem 의 상태 업데이트가 아직 완료되지 않아 빈 객체가 들어갈 수 있다.

이렇게 설명하면 좀 말이 된다. 하지만 확신하기엔 나는 모르는 게 너무 많으니 이걸 검증하는 것은 일단 뒤로 미뤄두고, 간단한 해결방법으로 state 를 하나 줄여보자.

해결 방법

긴 긴 시간을 저 코드를 째려보고 있었더니, todoItem 상태가 꼭 필요하지 않겠다는 생각이 들었다. 이유는 다음과 같다.

  1. 어차피 아이템의 데이터는 다른 방법으로도 가져올 수 있고
  2. 리렌더링이 필요한 시점은 새로운 할 일이 추가되었을때이며
  3. 이건 todoList 상태를 업데이트함으로써 충분히 가능하다.

그렇다면 todoItem 지우고 함수를 조금 가다듬어보자. 하는 김에 함수형 업데이트를 사용해보자. 이 경우에 꼭 필요한지는 모르겠지만 연습 삼아서..

const handleSubmitBtn = (e) => {
    e.preventDefault();
    const title = document.querySelector("#title");
    const content = document.querySelector("#content");
    if (title.value.trim() && content.value.trim()) {
      const item = {
        id: Date.now(),
        title: title.value,
        content: content.value,
        isDone: false,
      };
      setTodoList(prevTodoList => [item, ...prevTodoList]);
    }
  };

이런 식으로 코드를 바꿔보았다. 그 결과!

억울하게도 멀쩡하게 등록되는 카드를 보았다. 하... 왜 진작 이 생각을 못했을까 란 생각이 계속 들었지만, 앞으로 조심하면 될 일이다. 그러면 될 일이다...

다음 포스팅에 계속됩니다.

profile
웹 프론트엔드 개발을 익히고 있습니다.

1개의 댓글

comment-user-thumbnail
2023년 11월 9일

다음 포스팅이 너무 기대되네요!

답글 달기