저번에 결국, DessertItem 컴포넌트와 선택하기 버튼을 따로 분리해서 만들기로 결정했다. 그래서 SelectButton이란 컴포넌트를 만들고 다음과 같이 App.js에서 사용하기로 했다.
return(
<div>
<div className="select-zone">
{dessertList[selectIndex]}
<SelectButton onSelect={selectDessert}/>
{dessertList[selectIndex + 1]}
<SelectButton onSelect={selectDessert}/>
</div>
</div>
)
dessertList에서 selectIndex와 그 다음 번째의 디저트들을 보여주고, SelectButton도 같이 보여준다. 이 때 onSelect prop으로 저번에 작성한 selectDessert메소드를 넘겨준다.
그러나 지금까지의 selectDessert메소드는 단순히 selectIndex를 2씩 증가시키는 기능만 했다. 그러면 이제 이 메소드에 선택한 애들을 골라담아주는 기능도 넣어야 한다.
1. 선택한 애들 담기 - 파라미터가 있는 메소드를 prop으로 주는 법을 배우다
처음에 고민이 됐던 건,.선택하기 버튼을 분리한 마당에 특정 디저트를 어떻게 선택버튼과 매칭시켜서 담게 할 것인가였다. 그러다가 간단히 해결될거란 생각이 들었던게, selectDessert의 prop으로 특정 디저트들을 보내면 될 것 같았다. 선택된 디저트들을 담을 selectedList라는 state를 만들고, 선택하기 버튼이 눌릴 때마다 해당 디저트들(정확히는 DessertItem 컴포넌트)를 거기에 추가해준다! 따라서 selectDessert메소드는 선택된 디저트를 파라미터로 받아 다음과 같이 처리하도록 했다.
const selectDessert = (item) => {
setSelectedList([...selectedList, item]);
let nextSelectIndex = selectIndex + 2;
setSelectIndex(nextSelectIndex);
};
근데 여기서 작은 고민이 생겼다. 현재 App.js에서 SelectButton은 다음과 같이 쓰이는 상태다.
return(
<div>
<div className="select-zone">
{dessertList[selectIndex]}
<SelectButton onSelect={selectDessert}/>
{dessertList[selectIndex + 1]}
<SelectButton onSelect={selectDessert}/>
</div>
</div>
)
onSelect라는 prop으로 selectDessert라는 메소드를 주는 모습. 그러나 내가 지금 원하는 건 onSelect라는 prop으로 "특정 디저트 아이템을 파라미터로 받는 selectDessert메소드"를 넘겨야 한다!! 근데 그렇다고 다음과 같이 수정하면 당연히 안 될 것이다.
<SelectButton onSelect={selectDessert(item)}/>
ㅋㅋㅋㅋㅋ 저렇게 되면 item을 인자로 쓰는 selectDessert메소드를 prop으로 넘기는 게 아니라 호출된 값?을 넘기는 식으로 쓰일 것이다. 내가 아무리 잘 몰라도 이 정도 상식은 있는 사람이다..와 이거 어떡하지? 어떻게 해야 하지? 라는 생각이 들었다. 모를 땐 역시 구글링이 답이라고, 검색해보니 금방 방법을 찾을 수 있었다.
우선, SelectButton의 prop으로는 selectDessert메소드에서 인자로 넘길 디저트와 selectDessert메소드를 따로따로 넘긴다.
<SelectButton item={dessertList[selectIndex]} onSelect={selectDessert}/>
그리고 이걸 SelectButton 컴포넌트에서 다음과 같이 익명함수를 활용하는 방식으로 짬뽕한다.
function SelectButton({item, onSelect}){
return(
<button onClick={() => {onSelect(item)}}>
선택하기
</button>
)
}
export default SelectButton;
와 어떻게 이런 방법을..?ㅋㅋㅋㅋㅋㅋ 정말 간단하다. 암튼 이렇게 선택된 애들을 따로 담는 부분은 해결했다.
2. 라운드 시스템(16강, 8강, 4강) 구현 - "useEffect 이래서 쓴다"를 배우다
이제 라운드 시스템만 잘 만들면 대략적인 틀은 다 만들게 된다. 처음 16개 중 8개만 고르면 8강을 진행하고, 거기서 또 4개를 고르면 4강을 진행하고 이런 식으로 가야 한다.
어떻게할까..고민했다. 16개중 8개를 고르는 순간 selectIndex가 14에서 16으로 바뀌기 때문에, 16으로 바뀐 순간 selectedList를 dessertList에 덮어씌우기로 했다. 그래서 다음과 같이 코드를 짰는데, 뭔가 이상했다.
roundOf는 state값으로 처음엔 16을 지닌다. setter함수(setSelectIndex)를 통해 selectIndex값을 2만큼 늘려주고 나서 selectIndex값이 16일 때의 동작을 정의한 것인데, 문제는 16이 된 순간에 바로 setter함수들이 동작하지 않는다는 것이었다. 뭔가 이상하다 싶어서 setSelectIndex 전후로 console에 selectIndex값을 찍어봤는데 같은 값이 나왔다. ...도대체 뭐지?
바로, 리액트의 setter함수는 비동기적으로 동작하기 때문이었다.
react는 state가 변경될 때마다 그 state를 갖는 컴포넌트를 새로 렌더링하는데, state를 바꾸는 작업이 한꺼번에 이루어지면 그만큼 많은 렌더링이 발생하므로 이를 방지하기 위해 일종의 대기열에 넣고 변경사항을 한꺼번에 적용한다. 식당을 예로 들면 여러 테이블로부터 주문이 들어올 때마다 그때그때 받는게 아니라 마지막 테이블이 주문할 때까지 다 기다리다가 한꺼번에 받는 식. 하나의 이벤트 핸들러에서 state변경이 여러번 이뤄진다면 이벤트가 끝날 시점에 state변경을 일괄적으로 진행한다고 한다.
따라서, 내가 한 방식처럼 같은 메소드에서 setter함수로 바꾸자마자 그 값의 변화에 따른 동작을 하는 것이 예기치 못한 오류?를 발생할 수 있었던 것.
해결하는 방법은 setter함수를 호출할 때 두번째 파라미터로 콜백함수를 넣는 것이었으나, 이 기능은 없어졌다고 한다. useEffect의 사용을 권장한다고 함!! 딱 구글링으로 이렇게 보자마자 무릎을 탁 쳤다. useEffect는 state가 변할 때마다 실행되도록 만들 수 있으니까!!
그래서 다음과 같이 작성해줬다.
useEffect(() => {
if (selectIndex === roundOf){
setDessertList(selectedList);
setSelectedList([]);
setSelectIndex(0);
// parseInt : 몫만 취해준다.(소수점 밑 버리고 정수형 값만 취함)
setRoundOf(parseInt(roundOf / 2));
}
}, [selectIndex]);
구동모습(테스트 목적으로 구분선 아래로는 선택한 디저트들이 보이도록 함)
정말 작은 개인플젝?이지만..정말 하기 잘 했다는 생각이 든다. 이렇게 내가 직접 만들면서 느낀 건 단순히 강의나 책으로 보는 것보다 더 오래 뚜렷하게 기억에 남을 것 같다. 잘 마무리지어야겠다.
'PROJECT > 개발일지' 카테고리의 다른 글
[백엔드 야생 도전일기] 스프링으로 회원가입/로그인기능 만들어보기 (2) (1) | 2023.01.28 |
---|---|
[백엔드 야생 도전일기] 스프링으로 회원가입/로그인기능 만들어보기 (1) (0) | 2023.01.27 |
[React project] EP04. 항상 볼 수 없는 사이 - 리액트에서의 조건부 렌더링 및 기본적인 css 작업 (0) | 2022.03.15 |
[React project] EP02. 순조로울 줄 알았던 항해의 첫 장애물 - missing dependency (0) | 2022.02.27 |
[React project] EP01. 가장 먹고 싶은 디저트 16강 웹 프로젝트를 시작하다 (0) | 2022.02.10 |