입력창에 추천 검색어를 띄워보자+최적화
| topics | 201 React |
| references | codesandbox.io/s/2c61zj |
| types | 실습 |
목적
- 닉네임 검색시 추천되는 닉네임이 뜬다.
- 네이버처럼 추천 닉네임이 하단에 보인다.
- 해당 닉네임 hover시 해당 부분이 조금 밝아진다.
- 위아래 버튼 누를 시 해당 부분이 조금 밝아진다.
기존 코드
https://codesandbox.io/s/2c61zj?file=/demo.js
해당 Mui 공식 예제를 조금 수정하여 만들었다.
// icon을 제외하고는 div 태그에 style을 준것.
<Search sx={{ flexGrow: 1, margin: 'auto', maxWidth: 400 }}>
<SearchIconWrapper>
<SearchIcon />
</SearchIconWrapper>
<StyledInputBase
placeholder="Search…"
inputProps={{ 'aria-label': 'search' }}
onChange={handleValue}
onKeyDown={onKeyPress} />
</Search>
1. 추천검색어 ui를 만들어 보자.
밑에 div태그에 styled을 집어넣은 <DropDownBox> 와 <DropDownItem>을 나열 했다.
<Search sx={{ flexGrow: 1, margin: 'auto', maxWidth: 400 }}>
<SearchIconWrapper>
<SearchIcon />
</SearchIconWrapper>
<StyledInputBase
placeholder="Search…"
inputProps={{ 'aria-label': 'search' }}
onChange={handleValue}
onKeyDown={onKeyPress}
/>
{recommendList.length !== 0 && (
<DropDownBox>
{recommendList.map((data, index) => (
<DropDownItem
key={data.memberId}
selected={index === selected}
onClick={() => {
navigate(`/${data.memberId}`);
}}>
{data.profileImage ? (
<Avatar
sx={{ width: 20, height: 20 }}
src={process.env.REACT_APP_API_URL + data.picture}
/>
) : (
<AccountCircleIcon sx={{ width: 20, height: 20, color: 'white' }} />
)}
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', ml: 1 }}>
{data.nickname}
</Typography>
</DropDownItem>
))}
</DropDownBox>
)}
</Search>
코멘트 리스트 정보가 있으면 하단에 dropdownitem을 추가하는것이엿다.
추천리스트는 잘나오지만 위치가 이상하다. 당연하다. <StyledInputBase> 태그 밑에 <DropDownBox>가 쌓이게되고 appbar안 Search안 에 해당 태그가 들어가있으니 두개의 height가 변경된다.
이문제를 해결하기 위해서는
위의 <Search> 태그의 position을 relative, <DropDownBox> 태그의 position을 absolute로 지정한다. 그리고 z-index를 높여주면
해당 <Search>태그는 원래 그 0번에 위치한것처럼 그자리에 <DropDownBox> 태그는 <Search>태그를 기준으로 하단에 위치하게된다.

거기에다가 마우스 올릴시 색갈을 다르게 할려면 css의 hover state시 background를 달라지게 하면 된다. 나의 경우 DropDownItem에 흰색에 투명도를 달리하여 표현하엿다.
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.5),
},
2. 추천 검색 기능을 만들어보자.
나의 경우
검색어 입력> 백에게 추천 닉네임 달라고 요청보냄 > 리스트형태로 추천 닉 받음 > 추천닉을 화면에 표시
의 과정을 거쳤다.
input(여기선<StyledInputBase>태그)의 onchange함수에 서버에게 추천 닉네임을 요청하여 recomendlist에 넣어줫다.
const handleValue = (e) => {//onchange에 넣은함수
getRecommendList(e.target.value);
};
const getRecommendList = value =>{
if (value !== '') {
authService
.getUserByKeyword(value)
.then(
(result) =>
result &&
setRecommendList(result),
);
} else {
setRecommendList([]);
}
};
3. api 호출을 줄여보자
현재상황에서 추천 리스트를 받아오는 함수는 입력된 값이 변경될 때 마다 사용한다.
하지만 이는 효율적이지않다. 사용자가 어느정도 입력을 멈췃을 때 값을 보내주도록 하면 이를 최적화할 수 있을 것이다.
이를 위해 debounce를 사용햇다. debounce는 입력주기가 끝나면 함수를 호출한다. 현상황에서는 입력이 끝나고 500ms뒤에 가장 최근 값을 호출할 것이다.
lodash를 이용하여 구현할 수 있지만 작자는 배우는 입장이기에 직접 구현했다.
const debounceFunction = (callback, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => callback(...args), delay);
};
};
const getRecommendList = debounceFunction((value) => {
if (value !== '') {
authService
.getUserByKeyword(value)
.then(
(result) =>
result &&
setRecommendList((list) =>
list.toString() !== result.toString() ? result : list,
),
);
} else {
setRecommendList([]);
}
}, 500);
타이머를 설정하고 타이머 이전에 콜백이들어오면 타이머를 클리어한 후 다시 타이머를 설정한다.
!! 하지만 함수가 동작하지 않는다. 이는 함수형 컴포넌트 내부함수는 컴포넌트가 리랜더되면 함수자체가 초기화된다고한다. 즉 다른 이벤트가 발생할때마다 계속 clearTimeout을 호출했던 것이다.
이럴 땐 useCallback 함수로 감싸면된다. 이는 콜백의 리턴값이 유지된다. 만약 디팬던시가 추가되면 그 값이 변할때마다 콜백을 리턴한다.
const getRecommendList = useCallback(
debounceFunction((value) => {
if (value !== '') {
authService
.getUserByKeyword(value)
.then(
(result) =>
result &&
setRecommendList(result),
);
} else {
setRecommendList([]);
}
}, 500),
[],
);
4. 키보드 입력도 추가 해보자
우리가 평소 네이버를 사용할때 생각을 해보자
우리는 네이버에 검색어를 치고 위아래 방향키를 이용하여 추천 검색어에 도달한뒤 엔터를 이용하여 검색한다.
input의 onkeydown이나 onkeypress를 이용하면 키보드의 입력을 받을 수 있다.
위 아래로 누를시 해당 컴포넌트 선택하기
const [selected, setSelected] = useState(0);
//위아래 키 입력시 selected 값변경
const onKeyPress = (e) => {
if (e.key === 'ArrowDown') {
if (selected === recommendList.length - 1) {
setSelected(0);
} else {
setSelected(selected + 1);
}
} else if (e.key === 'ArrowUp') {
if (selected === 0) {
setSelected(recommendList.length - 1);
} else {
setSelected(selected - 1);
}
}
};
//추천리스트가 변할시 0으로 초기화
useEffect(() => {
if (recommendList.length === 0) return;
setSelected(0);
}, [recommendList]);
selected라는 변수는 몇 번째 인덱스가 선택됏는지 구분하는 state다. (selectedIndex가 더좋은 이름인듯하다..)
event의 key속성에 접근하여 위아래 버튼을 인식하고 index를 변환시켰다.
그리고 해당 인덱스의 <DropDownItem>일 시 배경색을 바뀐다.
구글과 네이버에서는 class를 추가하고 삭제하며 스타일을 적용햇다. 하지만 나는 해당 스타일이 적용되는 부분은 저부분 밖에 없기에 그냥 props로 전달하였다
마우스를 hover하거나 키보드로 해당 컴포넌트를 선택시 over라는 클래스가 추가되는 것을 볼 수 있다.
해당 컴포넌트를 디버깅해보면
이렇게 addClass가 작동하는 것을 볼 수 있다.
엔터시 submit하기
const onKeyPress = (e) => {
if (e.key === 'Enter') {
if (recommendList.length === 0) return;
navigate(`/${recommendList[selected].memberId}`);
} else if (e.key === 'ArrowDown') {
if (selected === recommendList.length - 1) {
setSelected(0);
} else {
setSelected(selected + 1);
}
} else if (e.key === 'ArrowUp') {
if (selected === 0) {
setSelected(recommendList.length - 1);
} else {
setSelected(selected - 1);
}
}
};
나의 경우 다른 페이지로 이동하엿다.
혹시 더 좋은 방법이 있다면 댓글을 부탁드립니다.