최적화 기법을 활용해서 검색하기를 구현하려는데, 일단은 기존에 구현했던 검색하기 컴포넌트를 이용해서 최적화를 해볼 것이다.
기존 웹 사이트에서 문제점은 로컬 서버라서 직접 DB에 연결을 해서 관리하는 형태라 실질적으로 큰 무리가 없는데 항상 개발할 때 pr을 날리게 되는데 예를 들어, 이런 검색과 관련된 기능이 있다면 코드에 대해서 여러 방향으로 개선을 할 필요가 있다. 어떤 pr을 해야 하냐면 네트워크 요청이 얼마나 가는지에 대해서 고려해야 한다. 기존 검색창에서는 입력을 할 때마다 네트워크 요청이 일어나고 있는 것을 알 수 있는데 만약에 이용자 수가 많아지면 그 데이터베이스에 무리가 많이 될 것이다. 물론 서버에서도 캐싱 같은 것들을 활용하기는 하지만, 이런 부분을 프론트 측면에서도 최대한 막아줘야 한다는 것이다. 대부분 백엔드가 뭔가를 막는다고 하면 프론트엔드도 방파제 역할을 해서 막아줘야 한다. 이 부분에 대한 소통이 정말 중요하다.
-> debouncing 개념을 도입.
⚽ 디바운싱
- 디바운싱이란? 예를 들어서 검색어를 하나 입력했다고 하자. 디바운싱 적용을 하지 않았을 때 요청을 보면 어떻게 요청을 하냐면 검색어의 한 글자당 요청이 갈 것이다.
- 실제로 네트워크 요청을 할 때 '오타니안' 이라고 검색을 하면 'ㅇ' 하나, 'ㅗ' 하나, 'ㅌ' 하나, 'ㅏ' 하나 이런 식으로 해서 총 9번의 불필요한 요청이 가게 될 것이다.
- 근데 검색을 통해 받고 싶은 것은 오타니안이 마지막에 쳤을 때나 아니면 풀네임이 기억 안 나서 '오타' 까지 밖에 기억이 안 나면 검색어 추천을 받고 싶을 수도 있다.
- 그럴 경우에는 어떻게 해야 하나.. 여기서 필요한 게 디바운싱이다 오타까지 입력하기 손을 뗐다고 하자. 디바운싱은 초 수 같은 걸 집어넣는다. (0.3초, 0.5초 후 등에 네트워크 요청) 지정한 시간이 지나면 네트워크 요청이 한번 일어나게 한다.
- 그러면 사용자는 오타 하고 해당 시간이 지나면 오타 관련 추천 검색어가 뜰 것이다. -> 몇 초 지연이 있어서 전부 다 입력하는 것에 네트워크 요청이 불필요하게 안 가는 현상을 막아준다.
디바운싱이랑 같이 나오는 개념으로 쓰로틀링이라는 개념이 있다. ( Throttling )
- 현재의 검색어 기능의 원리 - 검색어를 입력하면, setSearch를 통해 상태가 계속 변화하니까 search 값이 바뀌면서 리렌더링이 계속 일어난다. (리액트의 리렌더링 조건과 같음.)
- 화면이 계속 바뀌는 이유는 기본적으로 쿼리는 캐싱이라는 것을 제공을 해준다. 만약 이 쿼리 키가 달라지면 화면이 바뀌면서 다시 쿼리 키에 맞는 데이터를 받아와야 한다.
- 그래서 만약에 새로고침을 하고 123을 검색하면 아마 퀴리 키에 1, 2, 3, 12, 123에 해당하는 쿼리 키들이 다 있는 것이다. -> 쿼리 키가 달라져야 한다는 게 정말 중요한 포인트다.
⚽ useDebounce 만들기
- hooks에 useDebounce를 생성해준다.
- 현재 const [ search, setSearch ] = useState('') 이런 식으로 사용하고 있는데 바운스를 쓸 때 어떤 식으로 전달을 하냐면 useDebounce를 사용해서 타이머를 적용해야 한다.
- 그렇기 때문에 이 훅에서 검색어의 원본을 받아올 것이고, 그 뒤에 몇 초에 한번 네트워크 요청을 할 것인지 시간을 적용해준다. 그렇게 구현된 것을 띄울 페이지에 호출할 것이다.
- value 타입은 기존에는 빈 문자열로 했기 때문에 string 타입이 맞다.
- 근데 이 타입이 string이 아닌 배열일 수도, null일 수도 있다. 그렇기 때문에 여기서 제네릭 타입을 써준다. 그리고 delay를 줄 것이다. 아까 언급한 시간(ms)을 의미한다. 이 두가지 value를 받아와서 관리를 해줘야 한다. (useState) -> const [debouncedValue, setDebouncedValue] = useState<T>(value)
- 이 debounce 상태가 언제 변경이 되야 하냐면 value 값이 달라지거나 아니면 delay 값이 바뀔 때 코드가 실행될 수 있도록 처리를 해줘야 한다. -> useEffect 적용.
- useEffect에서 handler를 만들어줘야 하는데 그 이유는 타임아웃을 했으면 cleaner function이 언마운트 됐을 때 계속 타이머가 돌아갈 수 있기 때문에 그 언마운트됐을 때 취소해야 하는 작업(clean up 해야 하는 함수 제공해야 한다.)도 진행해야 한다.
- 기존에 시작된 타이머를 value가 바뀌거나 이러면 기존 타이머를 지워서 업데이트를 취소한다. -> cleanUp function에 대해서 이해하는 것이 필수적으로 중요하다. (강의 useEffect에서 부수효과를 처리하는 방법 참고.)
- return () => clearTimeout(handler); // 값이 계속 바뀔 때마다 마지막에 멈춘 값만 업데이트 된다.
- 의존성 배열에는 value랑 delay에 따라 useDebounce가 달라지기 때문에 value, delay를 추가해준다.
- 최종적으로 '잠시 기다린 후의' 값을 반환을 한다.
import { useEffect, useState } from "react";
function useDebounce<T>(value: T, delay:number) {
const [ debouncedValue, setDebouncedValue ] = useState<T>(value);
// value, delay가 변경될 때마다 실행
useEffect(() => {
// delay (ms) 후에 실행된다.
// delay 시간 후에 value를 debouncedValue로 업데이트하는 타이머를 시작합니다.
const handler = setTimeout(() => setDebouncedValue(value), delay);
// value가 변경되면, 기존 타이머를 지워서 업데이트를 취소합니다.
// 값이 계속 바뀔 때마다 마지막에 멈춘 값만 업데이트 됩니다.
return () => clearTimeout(handler);
}, [value, delay]);
// 최종적으로 '잠시 기다린 후의'값을 반환합니다.
return debouncedValue;
}
export default useDebounce;
- 이제, Homepage.tsx에 들어가서 몇 초 후 데이터 요청이 가도록 처리를 해줄 것이다.
- const debouncedValue = useDebounce(search, 300);
const {
data:lps,
isFetching,
hasNextPage,
isPending,
fetchNextPage,
isError
} = useGetInfiniteLpList(1, debouncedValue, sortOrder); // 기존 search를 debouncedValue로 갈아낀다.
- 네트워크 요청을 보면 위에서 입력한 0.3초가 지나면 그제야 요청이 가는 걸 볼 수 있을 것이다.
- 만약에 10000으로 설정하면 10초 뒤에 요청이 가게 될 것이다. 너무 느리게 해버리면 오류라고 판단될 수도 있으니 가능하면 길게 설정 안 하는 게 좋을 것이다.
- 사용자가 만족할 정도의 속도로 지정해줘도 될 것이다.
- useDebounce에서 나중에는 delay 부분이 아주 중요하기 때문에 상세화를 시키는 것도 좋다. -> constants에 delay.ts를 만들어준다.
export const SEARCH_DEBOUNCE_DELAY = 500;
const debouncedValue = useDebounce(search, SEARCH_DEBOUNCE_DELAY);
'UMC 8th Web 워크북' 카테고리의 다른 글
🔧Redux-Toolkit을 활용해서 쇼핑 카트 만들기 (0) | 2025.05.29 |
---|---|
🍎 useReducer를 왜 쓸까 (0) | 2025.05.29 |
🌐낙관적 업데이트 (Optimistic Update) (0) | 2025.05.15 |
💊useMutation으로 서버 상태관리 쉽게 (0) | 2025.05.14 |
♾️무한스크롤 + 스켈레톤 UI (4) | 2025.05.09 |