주말도 없이 달려왔더니 어느새 2주차를 맞이하게 되었다. 정신없이 준비하느라 정리는 안됬어도 목표한 기능 구현은 거진 끝나가고 있었지만, 사이트의 상품 목록 리스트 페이지와, 상세페이지가 아직 완성이 되지 않고 있었다는 것을 조금 뒤늦게 파악하게 되었다.
사실 이런점이 PM 으로서 반성해야 할 부분이라 생각한다. 잦은 회의를 했지만 정작 중요한 걸 빼먹은 느낌
뒤늦게라도 알았으니 추가적으로 구현할 부분과 현재 완성되지 않은 부분들에 대해서 다시 팀원들과 이야기를 하면서 조정해봤다. 결론적으로 추가구현은 거진 하지 않는쪽으로 방향을 잡고, 지금까지 작업한 부분들에 대해 좀 더 리펙토링 할 부분이 있다면 리펙토링을 하는 방향으로 가기로 했다. 근데 신기하게도 이게 너무 자연스럽게 결정되어서 제안한 나도 놀랐다. 다들 너무 지쳤나보다.
다만 몸에 지쳤다고만 해서 추가 기능 구현을 안하겠다고 결정지은것은 아니었다. 다들 뭐 말을 꺼내진 않았지만, 자신이 맡은 기능도 자신이 없었을 것이며 당연하게도 자신이 맡지 않은 페이지에 대해선 더더욱 그러하였기에, 뭔가 회의감(?) 같은것을 느낀것이 아닐까.. 싶다.
뭔가 배우고는 있는데 그저 앞만보고 달려가는 느낌을 받았을것이라 생각했고, 나중에 좀 따로 저녁도 먹으면서 얘기도 해보니 같은 마음이더라.
이러저러한 이유로 좀 더 지금까지 구현했던 기능들을 다잡는 방향으로 2주차를 진행하려 하였고, 아직 못한 상세페이지를 좀 더 집중적으로 살펴보고자 했다. 더군다나 2주차때는 추후 발표자료도 준비했어야 했기에, 1주차때보다 훨씬 촉박함을 느끼고 있어서 배워가는 것을 목적으로 하는 프로젝트이니 만큼 최대한 잘 습득하는 방향으로 결정한것이다.
배워가는 것이 목표니깐
이렇게 방향을 잡고있는데, 문득 한가지만 추가한다면 이걸 꼭 해보고 싶었던 것이 떠올랐었다. 기존 사이트에는 없는 검색기능.
상품을 구매하기 위해서 상단 바의 SHOP 탭을 클릭하여 리스트로 가는것이 아니라, 바로 검색을 하여서 한번에 상세페이지로 넘어가는 기능은 정말로 '추가' 해보고 싶었다. 메인 목표는 아니었지만, 약간 벅차도 한번 해보자고 팀원들에게 얘기했고, 팀원들도 딱 한가지의 기능 추가라 그런지 이부분에 대해 동의를 하였다.
Nav 컴포넌트에 Search 기능을 추가..
검색바가 어디있음 좋을까 생각하다가 역시 상단 nav 에 있는것이 가장 좋을 것이라는 판단을 하고, 기존에 존재하였단 Nav 컴포넌트에 추가하기로 하였다. 간단할 것이라 판단했는데, 이게 생각보다 바꿔주고 추가해줘야 하는 부분이 많았다. 간략히 요약하자면
- Search가 클릭되면, 메뉴 텝 일부가 사라지고 검색바로 대체된다.
- 검색을 시도하게 되면, 화면 중간부분까지 모달창으로 덮고 그 모달창에 검색 결과를 4~5개 정도 가져온다.
- 검색 결과를 클릭 시 상세페이지로 바로 넘어간다
- 테라로사의 상단 nav 는 2가지 타입이기에 검색기능은 2가지를 추가해야 한다.
이 모든 기능을 한 사람이 다 담당하기에는 무리라 판단이 들었다. 그래서 원래 nav 를 담당하는 팀원과 함께 작업을 하기로 결정했다. 일단 결과물을 먼저 보면 이해가 좀 더 쉬울 것 같으니...
어떻게 서버로부터 데이터를 받아와야 하나
전체적인 레이아웃과 기능은 팀원분께서 만드셨고, 이제 검색기능을 추가하는 일이 남이 있었다. 고민을 해봤었는데, 검색창에 입력어가 입력되자마자 서버와 통신이 되어야 겠다고 판단했다. 우선 입력 value 를 백엔드와 연동시키는 작업이 우선이었다.
async function request() {
const res = await fetch(
`${CONFIG_URL}/products/main/search?keywords=${search}`
);
const result = await res.json();
setValues(result.result);
}
useEffect(() => {
request();
}, [search]);
검색창에 입력되는 값은 search state 에 저장이 된다. search 값을 useEffect 의 의존성 배열에 추가하여 계속해서 request를 실행시키고, 계속 서버와 연동을 하여 데이터 값을 가져온다. 이 부분은 벡엔드와 합의가 된 부분으로 서버에서 자체적으로 encoding 하여 검색어를 해석하여 그에 맞는 데이터를 보내준다. 인코딩 과정이 좀 어려웠지만, 백엔드 팀원의 놀라운 성과로 인해 결국 위 시연처럼 잘 작동하는 것을 확인할 수 있다.
즉, 전체 데이터를 한번에 받아와 그 안에서 필터 기능으로 검색을 하는 것이 아니라, 검색하는 순간순간 서버와 연결하는 방향이라 좀 더 나아간 느낌이 들었다. 서버로부터 받은 데이터로 map 을 돌려서 화면에 렌더링을 하면서 검색 기능을 일단 구현은 했다.
Debounce
확실히 검색기능을 시연할 정도로 구현하긴 하였지만, 일단 첫 테스트부터 상당한 렉이 걸렸다. 좀 당황했던게 그렇게 많은 데이터를 가져오는 게 아닌데도, 뭔가 끊키는 느낌도 들고 실제로 느려진것도 맞아 보였다. local 로 와이파이 연결을 하는 터라 그럴 수 있다고 처음에는 판단했는데, 사실 원인을 살펴보면 매 검색을 할때 키보드 키 하나하나 누를때마다 그리고 지울 때마다 다 서버에 전송을 하고 있으니 서버가 감당 못하는것도 당연한건가 싶었다.
해결 방안을 찾던 도중 Debounce 라는 개념을 이용한다는 것을 알게 되었는데, 이미 약속된 발표기간이 다가와 일단은 구현하지 못하고 넘겼다. 다만 발표 이후 꼭 리펙토링을 해봐야겠다고 생각하여 이후 방법을 찾아봤다.
간략하게 Debounce 는 이벤트를 그룹화 한 뒤 특정 시간이 지나고 마지막 하나의 이벤트만을 호출하는 기술이다.
이렇게 말로 설명하면 잘 와닿지 않을 수 있을 텐데, 연속적으로 8번을 클릭하던 10번을 클릭하던 맨 마지막 클릭만을 호출한다고 생각하면 이해가 될 것이다.
이 개념이 어떻게 검색기능에서 응용될 수 있을까?
Debounce 를 응용하면 우리가 타자를 타이핑 할 때는 이벤트가 발생하지 않다가 타이핑을 멈추는 순간 이벤트가 발생하게 할 수 있다. 즉 마지막 타자를 친 시점에서 약간의 딜레이를 부여한다면 더욱 더 확실하게 한번만 이벤트를 호출하는 것이다.
여기서 이벤트는 검색에 있어서 서버와의 통신을 위한 fetch 함수다. 즉 어떠한 타이핑을 해도 마지막 타이핑때만 서버에게 데이터를 요청한다면, 훨씬 효율적으로 서버의 과부하를 막을 수 있을 것이다.
구현을 해보기 위해서 여러 방법을 알아보던 중, 커스텀 훅을 이용하여 직접 만드는 것이 있다는 것을 알게 되었다. Debounce의 커스텀 훅은 기본적으로 밑에 코드처럼 구성되어있다.
import { useState, useEffect } from "react";
export const useDebounce = (value, delay) => {
const [debounceValue, setDebounceValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebounceValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debounceValue;
};
커스텀 훅인 useDebounce 이다. 인자는 크게 입력값과 딜레이시간을 받게 되고, return 값으로 debounce 된 state 를 반환한다. 생각보다 작동 원리는 간단하다. useEffect로 setTimeout 을 발동시킨다. 그러니깐
- 우리가 검색한 value 가 인자로 전달된다 -> useDebounce는 같은 값을 리턴하지만 딜레이를 주고 리턴을 시킨다 (이게 핵심)
다만 clear effect 로 매 setTimeout 을 지워주는 것을 잊지 말자. 의존성 배열은 당연히 받아오는 인자를 넣어주면 된다.
이를 이용하면 충분히 딜레이를 주고 서버와 연동을 시킬 수 있는데, 적용도 간단하다.
const [search, setSearch] = useState('');
const deBounceValue = useDeBounce(search, 500);
async function request() {
const res = await fetch(
`${CONFIG_URL}/products/main/search?keywords=${deBounceValue}`
);
const result = await res.json();
setValues(result.result);
}
useEffect(() => {
request();
}, [deBounceValue]);
Import 하는 것을 잊지 말고, 위 코드처럼 적용하면 된다.
혹여나 검색어를 입력하는 도중 0.5초(딜레이) 가 지나기 전에 또 입력하면 어떻게 될까 궁금할 수 있는데, 만일 그 사이에 또 입력을 하게 되면 value 값이 변화되었으니, useEffect 에 의해 기존 setTimeout 이 지워지고, 이후 새로운 것을 실행시키기 때문에 여전히 딜레이는 초기화 된다. 그래서 문제없이 작동하게 된다.
좀 더 테스트가 필요할 수 있으나 리펙토링을 하게 되니 훨씬 실용성을 가지는 검색창이 된 것 같아 기분이 좋아졌다.
다시 한번 돌아보며
2주차에는 검색을 제외하면 정리하느라 시간을 다 보내게 되었지만, 나 자신이 성장하고 있다고 느껴진 부분은 1주차도 좋았지만 2주차도 역시나 좋았다. 그리고 막연했던 코딩 공부에 대해서 조금 더 방향성을 찾게 된 느낌이라 이번 1차 프로젝트를 잘 끝냈다는 후련한 마음도 드는 2주였다.
일이 풀리고 풀리지않고는 사실 개발이 아니더라도 살면서 많이 느껴본 경험이고, 지금 나에게 중요한것은 내가 이를 통해 얼마나 성장할 수 있을지 여부였는데, 풀리지않는 어려움들을 어떻게든 구현해내고 거기서 끝내지 않고 리펙토링을 통해서 좀 더 유저 친화적이거나 서버 친화적이던가 급하게 구현만 하느라 고려하지 않은 부분까지 고민하는 과정이 성장에 필요했구나 하고 느껴졌다.
팀원들을 컨트롤 하면서도 많이 배우게 되었다. 나와 다른 시작점을 가진 사람들을 파악하고, 유연성있게 계획을 세워나가는 과정이 쉽지 않았어도, 앞으로 같은 경험을 하게 될 때 훨씬 더 자신있게 프로젝트를 이끌어 나갈 수 있을거라는 생각이 든다. 하기 싫어했던 PM 이지만 그래도 경험을 해보자 다짐했었는데, 역시나 뭐든 부딛쳐 보면 좋은점이 참 많은것 같다.
이제 바로 2차 프로젝트를 시작하는데, 얼마나 더 성장을 할 수 있을지 궁금해지기 시작했다.
참고문헌
https://webclub.tistory.com/607
'Programing > React' 카테고리의 다른 글
2차 프로젝트 - 에어비엔비 상단 검색창 - Search(2) (0) | 2022.08.30 |
---|---|
2차 프로젝트 - 에어비엔비 상단 검색창(1) (0) | 2022.08.24 |
테라로사 사이트 클론 프로젝트 - 1주차 (Cart Page) (0) | 2022.07.31 |
테라로사 사이트 클론 프로젝트 - 1주차 (Login) (0) | 2022.07.31 |
테라로사 사이트 클론 프로젝트 - 1주차 (Main Page) (0) | 2022.07.30 |