며칠 전 React 입문주차 첫번째 과제가 마무리되었다. 어려웠던 점도 있었고, 뿌듯했던 점도 있었다. 과제를 전체적으로 돌아보자.
유일하게 배웠고 쓸 수 있는 hook이 useState
뿐인데... 이놈이 이번에도 많이 괴롭혔다.
state
생성state
참조 및 setState
사용이 정도로 다뤄보자.
state
생성강의를 들을 때엔 막힘없이 술술 진행할 수 있었는데 과제로 주어지고 보니 state가 뭐고 useState는 어디에 쓰는거고... 역시 코드는 실천이다. 써 보기 전까지는 아는 게 아니다...
제대로 생각을 해보자. 이거저거 다 state를 쓸 수는 없다. 어떤 side effect가 있을지는 모르지만 일단
state
가 많아지면 → 리렌더링 횟수가 늘어나고 → 부하가 커질 것이다
라고 처음엔 생각했지만, 실제로 리렌더링 횟수가 좀 늘어난다고 퍼포먼스에 유의미한 영향을 미치진 않는 것으로 보인다. 정확한 데이터는 없지만... 문제가 되는 부분은 말 그대로 무한 렌더링이 일어나 페이지가 제대로 로드될 수 없는 정도겠지. 그러므로 state
를 많이 만든다고 해서 기능상의 큰 문제는 없을 것이다. 하지만 일단 덮어놓고 state
로 선언하는 일만은 피해야 할 것이다. 물론
state
를 제대로 예상할 수 있지는 않을 것이다.같은 생각도 들었지만 시간 여유가 있는 만큼 조건이라도 설정하기로 한다. 이런 저런 생각을 거쳐 뽑아낸 조건은 다음과 같다.
function Todolist() {
const [todoItem, setTodoItem] = useState({});
const [todoList, setTodoList] = useState([]);
위의 세가지를 고려해서 처음 구상했던 state
의 목록은 todoItem
과 todoList
의 두 개였다. 필요에 따라 더 추가할 수 있겠지만 우선 이 상태들을 가지고 앱을 만들어나갔다.
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
객체로 설정하고 그 todoItem
을 setTodoList
의 파라미터로 전달한다. todoList
state
의 맨 앞에 넣고 기존의 원소를 spread 해서 새로운 배열을 todoList
로 설정하는 기초적인 코드이다. 그런데 이렇게 아이템을 넣고 생성되는 할 일 항목들을 보니 문제가 있었다. 두 개의 아이템을 넣었는데 넣은 적 없는 빈 아이템이 있는 것이다.
한참을 여기에 막혀있었다. todoItem
에 대한 상태를 만들 때 초기값을 빈 객체로 주었는데, 어찌된 이유에선지 setTodoItem(item)
으로 내용을 채운 객체로 업데이트했는데도 매번 첫 번째 아이템이 빈 객체로 들어갔다... 여기서 시간을 얼마나 썼는지.... 여기저기 찾아도 봤지만 별다른 소득이 없었고, ChatGPT에게 물어보자 알쏭달쏭한 답변을 내놨다.
React에서
useState
와 상태 업데이트는 비동기적으로 처리된다.
흠... 나는 다양한 경험을 통해 ChatGPT를 너무 믿으면 안 된다는 것을 배웠다. 그래도 얘가 없는 이야기는 잘 안 하니까... 맞는 말을 했다고 가정한다면
handleSubmitBtn
함수에서는 두 개의 서로 얽혀있는, 다시말해 todoList
가 상태 업데이트 함수 setTodoList
에서 다른 상태 todoItem
을 참조하는 식으로 상태가 업데이트 되고 있다. todoList
를 업데이트 할 때 todoItem
의 상태 업데이트가 아직 완료되지 않아 빈 객체가 들어갈 수 있다.이렇게 설명하면 좀 말이 된다. 하지만 확신하기엔 나는 모르는 게 너무 많으니 이걸 검증하는 것은 일단 뒤로 미뤄두고, 간단한 해결방법으로 state
를 하나 줄여보자.
긴 긴 시간을 저 코드를 째려보고 있었더니, todoItem
상태가 꼭 필요하지 않겠다는 생각이 들었다. 이유는 다음과 같다.
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]);
}
};
이런 식으로 코드를 바꿔보았다. 그 결과!
억울하게도 멀쩡하게 등록되는 카드를 보았다. 하... 왜 진작 이 생각을 못했을까 란 생각이 계속 들었지만, 앞으로 조심하면 될 일이다. 그러면 될 일이다...
다음 포스팅에 계속됩니다.
다음 포스팅이 너무 기대되네요!