[현장실습_그 후] React + Node.js 프로젝트 - 5월 1주차

·2022년 5월 2일
0

현장실습

목록 보기
11/12
post-custom-banner

목표

자동완성 기능 구현

상세

자동완성 UI 생성

  • 검색창에 검색어 입력시 자동완성 UI 생성

    • 검색어 없을 때, 다른 곳 클릭 했을 때 UI창 사라지기

서버로 검색어 전송

  • 검색어 입력할 때마다 서버로 검색어 전송

  • 비동기 / get


서버에서 데이터 받아오기

  • 게시글 리스트 중 검색어가 포함된 게시글 데이터 클라이언트로 전송


자동완성 UI에 서버에서 받아온 데이터 넣기

  • 자동완성 UI에 서버에서 받아온 데이터 넣기


이벤트 처리

  • 자동완성에 있는 검색어 클릭 시 해당 검색어로 바로 검색
  • 검색창에 포커스가 있을 때, 키보드 아래, 위 키 누를 시 자동완성 목록 위 아래로 순회

저번주에 발생했던 파일 다운로드 문제를 아직 해결하지 못했다. 이 부분만 잡고 있으면 다른 걸 진행 못할 것 같아서 다른 기능을 구현하면서 동시에 찾아보기로 했다.

  • 자동 완성 UI 생성
  • 비동기로 서버에 검색어 전송
  • 서버에서 검색어로 검색한 결과 10개를 클라이언트로 전송
  • 자동 완성 UI에 서버에서 받아온 데이터 넣어서 생성
  • 검색어 존재 여부에 따라 자동 완성 UI toggle

  • 검색창, 자동완성 UI 외 다른 곳 클릭 시 자동완성 UI 닫히기
  • 자동완성 UI에 서버에서 받아온 데이터 추가하는 로직 리팩토링
  • 키보드 위 아래 키 눌렀을 때, 자동완성 데이터 순회하기

현재 위치를 나타내는 targetNum을 state로 만들고 위 방향키, 아래 방향키가 눌릴 때마다 targetNum을 증가/감소 시키면서 데이터를 순회하도록 했다.

🙋‍♀️자동완성 데이터 순회하기

원하는 결과

  • 아래 방향키 누름 : 다음 데이터 선택
  • 위 방향키 누름 : 이전 데이터 선택

되면서 전체 데이터를 빙글빙글 순회

실제 결과

  • 아래 ➡ 아래 ➡ 위 / 위 ➡ 아래 와 같이 방향이 바뀔 때, 이전에 진행하던 방향으로 움직임

원인 : setState()

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 개발 문서를 읽으면서 정리하기 시작했다. (한글 번역은 가끔씩 참고 정도만...)

이렇게 한 주 목표를 정하고 하는 걸 너무 늦게 시작해서 다음주부터는 이전에 빠뜨린 기능을 위주로 작업하고, 에러 고치고 리팩토링 하면서 마무리 할 것 같다.


참고 자료

profile
익숙함을 향해👟
post-custom-banner

0개의 댓글