자동완성 기능 구현
검색창에 검색어 입력시 자동완성 UI 생성
검색어 입력할 때마다 서버로 검색어 전송
비동기 / get
게시글 리스트 중 검색어가 포함된 게시글 데이터 클라이언트로 전송
자동완성 UI에 서버에서 받아온 데이터 넣기
저번주에 발생했던 파일 다운로드 문제를 아직 해결하지 못했다. 이 부분만 잡고 있으면 다른 걸 진행 못할 것 같아서 다른 기능을 구현하면서 동시에 찾아보기로 했다.
현재 위치를 나타내는 targetNum
을 state로 만들고 위 방향키, 아래 방향키가 눌릴 때마다 targetNum
을 증가/감소 시키면서 데이터를 순회하도록 했다.
원하는 결과
- 아래 방향키 누름 : 다음 데이터 선택
- 위 방향키 누름 : 이전 데이터 선택
되면서 전체 데이터를 빙글빙글 순회
실제 결과
- 아래 ➡ 아래 ➡ 위 / 위 ➡ 아래 와 같이 방향이 바뀔 때, 이전에 진행하던 방향으로 움직임
setState()
는 비동기로 동작하면서 바로 값을 바꾸는게 아니라 이전의 리액트 엘리먼트 트리와 전달받은 state가 적용된 엘리먼트 트리를 비교하는 작업을 거치고, 최종적으로 변경된 부분만 DOM에 적용한다.
그 과정에서 onKeyUp
메소드 안에서 바로 setState()와 targetNum
에 있는 데이터 엘리먼트의 backgroundColor를 바꿨을 때, targetNum
이 반경이 된 상태가 아니었기 때문에 제대로 동작이 안됨.
자동완성 UI에 서버에서 받아온 데이터를 state로 만들어 state를 통해 li 엘리먼트를 생성하도록 수정
targetNum
과 현재 데이터의 index가 같으면 선택되었다고 판단
{autoData.length < 0 ? null : autoData.map((data, index) => {
const styles = {
display : 'block',
width : '100%',
padding : '0.5rem 1rem'
}
return <li key={data.POSTID} className="autoComplete"
style={targetNum === index ?{
...styles,
backgroundColor : '#e9ecef'
} : {
...styles,
backgroundColor : '#fff'
}}
>
{data.TITLE}
</li>
})}
기존 구현
getElementsByTagName()
로 html태그를 찾아서 click 이벤트를 걺- 클릭한 엘리먼트에
autocomplete
클래스가 없으면 외부 영역이라고 판단해 자동완성 창 닫기- 자동완성 기능을 다른 컴포넌트로 만들어 자식 컴포넌트로 사용하게 되면서 외부 컴포넌트에서 내부(자동완성) 컴포넌트의 HTML 엘리먼트에 접근하게 되었기 때문에 리팩토링을 결정
구현 방법
forwardRef()
사용해 외부 컴포넌트에서 자동완성 컴포넌트로 ref를 전달- 자동완성 컴포넌트에서 ref를 받아 외부와 내부를 결정할 영역을 설정
- 자동완성 컴포넌트 전체를 영역으로 설정
- 외부 컴포넌트에서
document
에 click 이벤트를 설정- ref로 내부 컴포넌트의 HTML 엘리먼트(검색 영역)에 접근해 클릭 한 엘리먼트가 검색 영역 안이 아니면 자동완성 창 닫기
//외부 컴포넌트 Navbar.js
...
import Autocomplete from "./Autocomplete";
function Navbar({session , setResult}) {
const autoRef = useRef(null);
...(생략)
useEffect(() => {
const handleClickOutside = (e) => {
if(autoRef.current && !autoRef.current.contains(e.target)) {
autoRef.current.children[1].style.display = 'none';
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
}
},[autoRef]);
}
return <div>
...
<Autocomplete isMulti={false} setResult={setResult} ref={autoRef}/>
</div>
//내부 컴포넌트 Autocomplete.js
function Autocomplete({isMulti, setKeyword, multiBtn, setResult}, ref) {
...(생략)
return <div ref={ref}>
<InputBase
sx={{ml: 2, flex: 1}}
placeholder="Search"
value={word}
inputProps={{className : 'autoComplete'}}
onChange={onChange}
onKeyUp={autoComplete}
/>
<ul ref={ul}
style={!isMulti ? {
...ulStyles,
fontSize : '1.2rem',
padding : '0.5rem 1rem'
} : {
...ulStyles,
fontSize : '0.7rem',
padding : '0.25rem 0rem',
marginTop : '0px'
}}
>
{autoData.length < 0 ? null : autoData.map((data, index) => {
return <li key={data.POSTID}
style={targetNum === index ? {
...liStyle,
backgroundColor : '#e9ecef'
} : {
...liStyle,
backgroundColor : '#fff'
}}
onClick={addClick}
>
{data.TITLE}
</li>
</ul>
</div>
}
export default forwardRef(Autocomplete);
기능들을 구현하면서 리액트에 대해 좀 더 깊게 알아야겠다는 필요성을 느꼈다. setState()가 비동기로 실행된다던가, 리액트에서는 HTML 엘리먼트에 직접 접근할 때 getElementBy..가 아니라 ref로 접근하는 것과 같은 걸 처음 알게 되었다.
그래서 영어 공부도 할겸 리액트에 대해서도 기본부터 차근차근 쌓을 겸 React 개발 문서를 읽으면서 정리하기 시작했다. (한글 번역은 가끔씩 참고 정도만...)
이렇게 한 주 목표를 정하고 하는 걸 너무 늦게 시작해서 다음주부터는 이전에 빠뜨린 기능을 위주로 작업하고, 에러 고치고 리팩토링 하면서 마무리 할 것 같다.