
자동완성 기능 구현
검색창에 검색어 입력시 자동완성 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 개발 문서를 읽으면서 정리하기 시작했다. (한글 번역은 가끔씩 참고 정도만...)
이렇게 한 주 목표를 정하고 하는 걸 너무 늦게 시작해서 다음주부터는 이전에 빠뜨린 기능을 위주로 작업하고, 에러 고치고 리팩토링 하면서 마무리 할 것 같다.