입력창에 추천 검색어를 띄워보자+최적화

topics 201 React
references codesandbox.io/s/2c61zj
types 실습

목적

  • 닉네임 검색시 추천되는 닉네임이 뜬다.
  • 네이버처럼 추천 닉네임이 하단에 보인다.
  • 해당 닉네임 hover시 해당 부분이 조금 밝아진다.
  • 위아래 버튼 누를 시 해당 부분이 조금 밝아진다.

기존 코드

https://codesandbox.io/s/2c61zj?file=/demo.js 
해당 Mui 공식 예제를 조금 수정하여 만들었다.
입력창에 추천 검색어를 띄워보자+최적화-1767269590991.png

 // 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을 추가하는것이엿다.
입력창에 추천 검색어를 띄워보자+최적화-1767269953168.png
추천리스트는 잘나오지만 위치가 이상하다. 당연하다. <StyledInputBase> 태그 밑에 <DropDownBox>가 쌓이게되고 appbar안 Search안 에 해당 태그가 들어가있으니 두개의 height가 변경된다.

이문제를 해결하기 위해서는 
위의 <Search> 태그의 position을 relative, <DropDownBox> 태그의 position을 absolute로 지정한다. 그리고 z-index를 높여주면

 해당 <Search>태그는 원래 그 0번에 위치한것처럼 그자리에 <DropDownBox> 태그는 <Search>태그를 기준으로 하단에 위치하게된다.
 입력창에 추천 검색어를 띄워보자+최적화-1767269983774.png
거기에다가 마우스 올릴시 색갈을 다르게 할려면 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로 전달하였다
입력창에 추천 검색어를 띄워보자+최적화-1767270056438.png

마우스를 hover하거나 키보드로 해당 컴포넌트를 선택시 over라는 클래스가 추가되는 것을 볼 수 있다.

해당 컴포넌트를 디버깅해보면
입력창에 추천 검색어를 띄워보자+최적화-1767270070739.png
이렇게 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);
        }
    }
};

나의 경우 다른 페이지로 이동하엿다. 

혹시 더 좋은 방법이 있다면 댓글을 부탁드립니다.