디바운싱
이벤트가 연속적으로 발생할 때 마지막으로 발생한 이벤트를 기준으로 일정 시간 후에 마지막 이벤트만 실행되도록 한다. 유사한 개념으로 쓰로틀링이 있으나 검색 기능에 적용하기에 디바운싱이 더 적합한 거 같아 적용해 보았다.
디바운싱 없이 input 값을 인식하는 경우
단순히 input 태그에 들어오는 값을 받을 경우 사용자가 입력하려는 검색어가 들어올 때 매입력마다 불필요한 리랜더링이 일어난다.
const Search = () => {
const searchHandler: React.ChangeEventHandler<HTMLInputElement> = e => {
console.log(e.target.value);
};
return (
<>
<form onSubmit={e => e.preventDefault()}>
<input type='text' placeholder='검색어 입력' onChange={searchHandler} />
</form>
</>
)
}
이 경우에 디바운싱을 적용시켜 볼 수 있다. 예컨대 '티스토리'를 검색하고 싶다면 입력 중에는 검색 결과를 요청하지 않다가 사용자가 '티스토리'를 모두 입력했을 경우에 요청한다. 이렇게 하면 리랜더링 횟수와 서버에 검색 결과를 요청하는 통신 횟수를 줄일 수 있고 더불어 사용자가 검색 버튼을 누르지 않아도 검색 결과를 바로 받을 수 있다.
적용 과정
1. 디바운싱 방식으로 핸들러 만들기
사용자가 원하는 검색 결과를 다 입력했다는 것을 알 수 있도록 타이머를 사용했다. 타이머를 만들고 매입력 마다 시간을 초기화시킨다. 더 이상 입력이 없는 경우 타이머가 끝까지 돌게 되고, 타이머가 끝나는 시점에 input창에 입력된 내용으로 검색 결과를 찾는다.
const searchHandler: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
clearTimeout(timeSet.current);
timeSet.current = setTimeout(() => {
setSearch(target.value);
}, 500);
// 500ms 가 지나도록 검색창에 입력이 없다면 콜백함수가 실행된다.
};
앞전의 예시처럼 '티스토리'를 입력하고자 하는 경우 매 입력마다 타이머가 초기화되다가 마지막 글자에서 타이머가 끝나면서 결과를 요청하는 것이다.
2. 검색 결과받아오기
타이머가 다되고 콜백이 실행됐다면 useEffect를 사용해 검색 결과를 찾도록 했다.
useEffect(() => {
(async () => {
const { data } = await axios.get<SearchList[]>('http://localhost:3001/info');
const result = data.filter(el => el.name.includes(search));
setSearchList(result);
})();
}, [search]);
콜백이 들어올 때마다 검색창에 있는 내용을 포함하는 결과를 모두 보여주게 된다.
3. 검색 결과가 없는 경우
검색 결과는 통신 후 filter를 통해 값을 받기 때문에 만약 검색 결과가 없다면 빈 배열을 받게 된다. 따라서 빈 배열인 경우 결과가 없다는 문구가 나오도록 하고 이를 위해서 useEffect문에 결과가 없는 경우를 추가해주었다.
useEffect(() => {
(async () => {
const { data } = await axios.get<SearchList[]>('http://localhost:3001/info');
const result = data.filter(el => el.name.includes(search));
if (result.length) {
setSearchList(result);
setSearchEmpty(false);
} else {
setSearchList([]);
setSearchEmpty(true);
}
})();
}, [search]);
오류
useRef의 타입으로 NodeJS.timeout을 주는 경우에 인식하지 못하는 경우
NodeJS를 인식할 수 없어 오류처리가 되어있는데 npm으로 type/node를 설치해주면 된다.
npm i @types/node --save
최종 코드
import axios from 'axios';
import { useEffect, useRef, useState } from 'react';
import { SearchList } from '../interface';
const Search = () => {
const timeSet = useRef<NodeJS.Timeout>();
const [search, setSearch] = useState('');
const [searchEmpty, setSearchEmpty] = useState(true);
const [searchList, setSearchList] = useState<SearchList[]>([]);
useEffect(() => {
(async () => {
const { data } = await axios.get<SearchList[]>('http://localhost:3001/info');
const result = data.filter(el => el.name.includes(search));
if (result.length) {
setSearchList(result);
setSearchEmpty(false);
} else {
setSearchList([]);
setSearchEmpty(true);
}
})();
}, [search]);
const searchHandler: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
clearTimeout(timeSet.current);
timeSet.current = setTimeout(() => {
setSearch(target.value);
}, 500);
};
return (
<>
<form onSubmit={e => e.preventDefault()}>
<input type='text' placeholder='검색어 입력' onChange={searchHandler} />
</form>
{searchEmpty ? (
<>
<p>검색결과가 없습니다</p>
</>
) : (
<ul>
{searchList.map(info => (
<li key={info.id}>{info.name}</li>
))}
</ul>
)}
</>
);
};
export default Search;
'Programing > React' 카테고리의 다른 글
[React] vite 프로젝트 + vercel 배포 후 새로고침 404 (0) | 2022.12.10 |
---|---|
[React] vite 프로젝트 만들기 (0) | 2022.11.03 |
[React] 소셜웹페이지 - 서버 통신 & 로그인 (0) | 2022.08.27 |
[React] 댓글 입력 오류 (0) | 2022.08.19 |
[React] 소셜웹페이지 - 자바스크립트 ➡️ 리액트 변환 (0) | 2022.08.17 |