debounce를 활용한 다수의 서버 요청 처리 경험기
기존 서비스에는 Input에 onBlur로 휴대폰 번호 중복 함수를 호출했다면
서비스 업데이트 이후부터는 onInput을 사용해 input에 입력되는 값에 반응하여
휴대폰 번호 중복 체크 함수를 호출하는 것으로 코드를 변경했습니다.
그러자 고려해야 할 사항이 발생했는데요.
유저가 빠르게 번호를 썼다 지웠다를 반복하면 서버에 많은 요청이 전달되고
많은 요청은 곧 많은 비용으로 이어지게 되어 잦은 요청에 대한 처리에 대한 고민이 필요했습니다.
debounce 처리 시도
잦은 유저의 변경값에 대해 마지막 입력값에 대하여 요청을 보내면 되므로
debounce를 선택했습니다.
하지만 debounce로 생성된 함수는 이전에 생성된 함수를 취소하고
새로운 함수를 생성하는 특징이 있는데요.
제가 useEffect 내부에서 휴대폰 번호 중복 체크 함수를 사용할 때마다
새로운 debounce 함수가 생성되며 이전에 생성된 debounce 함수 실행이 취소되었는데
useCallback을 사용해 함수 재사용을 시도했고 그 결과 여러 번 호출을 시도해도
마지막 호출만 이뤄지도록 처리가 되었습니다.
// react-hook-form을 사용하고 있다.
const phoneNumber = watch('phoneNumber')
const 핸드폰번호중복함수 = useCallback(
debounce(() => {
if (phoneNumber.length === 11) {
폰번호서버요청함수({ data: { phone: phoneNumber } });
}
}, 300),
[],
);
useEffect(() => {
if (phoneNumber.length === 11) {
핸드폰번호중복함수();
}
}, [phoneNumber, 핸드폰번호중복함수]);
문제 발생
하지만 기능이 동작하지 않았는데요.
useCallback의 의존성 배열이 있는데, 저는 빈 배열로 전달해
최초 렌더링 시 한 번만 함수가 생성되고 이후엔 함수가 생성되지 않아
휴대폰 번호 Input을 입력해도 휴대폰 번호 중복 함수가 호출되지 않았습니다.
그렇다고 useCallback의 의존성 배열에 Input값을 넣는다면 번호가 변경될 때마다
핸드폰번호중복함수가 호출되기에 useCallback과 debounce를 사용한 의미가 없게 되어버리죠.
1차 해결 시도
이어 생각해 본 것은 debounce 함수를 useCallback 외부에서 사용해 보자! 였습니다.
하지만 문제 해결은 되지 않았어요. 콘솔로 확인하며 그 이유를 파악해 보았습니다.
[인자를 전달하지 않았을 때]
const phoneNumber = watch('phoneNumber')
const 핸드폰번호중복함수 = useCallback(
debounce(() => {
console.log('2: ', phoneNumber )
if (phoneNumber.length === 11) {
console.log('3: ', phoneNumber )
폰번호서버요청함수({ data: { phone: phoneNumber } });
}
}, 300),
[],
);
useEffect(() => {
if (phoneNumber.length === 11) {
console.log('1: ', phoneNumber )
핸드폰번호중복함수(); // 1️⃣ 외부 scope의 phoneNumber 전달
}
}, [phoneNumber, 핸드폰번호중복함수]);
우선 처음 제가 생각했던 코드 흐름을 설명드리자면
외부 변수로 phoneNumber를 만들어두어 어디서든 해당 변수를 참조할 수 있을 줄 알고
useEffect와 핸드폰 번호 중복 함수에서 인자로 전달하지 않고 사용했습니다.
그런데 콘솔을 확인해 보면 핸드폰 번호 중복 함수에서는 phoneNumber라는 변수에
할당된 값이 undefined로 나타나는 것을 확인할 수 있습니다.
심지어 실제로 서버에 값이 전달되는 함수에 값이 전달이 안되고 있네요.
2차 해결 시도
그렇다면 함수의 argument에 phoneNumber를 전달해 주면 되겠다 판단했습니다.
역시, 이게 정답이었네요!
// react-hook-form을 사용하고 있다.
const phoneNumber = watch('phoneNumber')
const 핸드폰번호중복함수 = useCallback(
debounce((phoneNumber) => { // 2️⃣ useEffect문의 함수에서 넘겨받은 phoneNumber
if (phoneNumber.length === 11) {
폰번호서버요청함수({ data: { phone: phoneNumber } });
}
}, 300),
[],
);
useEffect(() => {
if (phoneNumber.length === 11) {
핸드폰번호중복함수(phoneNumber); // 1️⃣ 외부 scope의 phoneNumber 전달
}
}, [phoneNumber, 핸드폰번호중복함수]);
저는 여기서 궁금한 점 하나가 생겼습니다.
왜 인자로 전달하니 성공한 걸까?
Debounce의 특성과 클로저
알고 보니 debounce의 콜백 함수는 외부 스코프의 변수를 직접 참조하지 않는 특성이 있었습니다.
또한 마지막 호출 시에 전달된 인자만이 debounce의 콜백 함수에 전달됩니다.
그렇기에 함수가 호출되는 시점의 phoneNumber값을 참조하려면 해당 값을
직접 인자로 전달해줘야 했던 이유가 여기 있었어요.
클로저는 해당이 없었습니다!
이렇게 기능 하나를 잘 마무리할 수 있었습니다.
하나씩 기능 개선을 해나가며 재미를 느낄 수 있어 기분 좋은 요즘이네요ㅎㅎ