[React] 검색창 구현하기 - focus에 따라 컴포넌트 렌더링

Inu·2023년 8월 28일
0

검색창 구현하기

목록 보기
2/3

다음과 같이 input에 focus가 되면 검색 결과창이 나타나고 바깥 영역을 클릭하면 결과창이 없어지도록 해보자.

focus, blur 이벤트 처리

리액트에서 focus 관련 이벤트를 처리하려면 onFocus, onBlur 속성에 핸들러를 전달하면 된다.

  • onFocus : 요소에 focus가 됐을 때를 처리
  • onBlur : 요소에 focus가 해제되었을 때를 처리

input에 focus가 되었는지를 나타내는 state인 isInputFocused를 만들고 관련 핸들러를 만들어 onFocus, onBlur에 전달해주었다.
그리고 isInputFocused에 따라 검색 결과창인 SearchRecommend를 조건부 렌더링했다.

export function Search() {
  const [isInputFocused, setIsInputFocused] = useState(false);

  const setRecommendVisible = () => setIsInputFocused(true);
  const setRecommendInvisible = () => setIsInputFocused(false);
  
  /* ... */
  
  return (
    <div>
      <SearchForm
        keyword={keyword}
        onChange={handleKeywordChange}
        onFocus={setRecommendVisible}
        onBlur={setRecommendInvisible}
      />
      {isInputFocused && <SearchRecommend />}
    </div>
  );
}
export function SearchForm(props: SearchFormProps) {
  return (
    <form>
      <InputBase>
        <InputWithIcon>
          <FaSearch />
          <Input
            placeholder="질환명을 입력해주세요."
            value={props.keyword}
            onChange={props.onChange}
            onFocus={props.onFocus}
            onBlur={props.onBlur}
          />
        </InputWithIcon>
        <Button>검색</Button>
      </InputBase>
    </form>
  );
}

처음에는 toggleRecommendVisible을 만들어서 state를 !isInputFocused로 업데이트를 했는데 예기치 못한 문제가 있을 수 있으니 focus, blur별로 따로 이벤트 핸들러를 만들어서 처리하는 것이 더 맞다고 생각했다.

display: none? 조건부 렌더링?

검색 결과창을 state값에 따라 조건부 렌더링하는 것으로 했는데 생각해보니 css 속성인 display: none;으로도 검색 결과창을 안보이게 할 수도 있다.

export const Container = styled.div<{ $isVisible: boolean }>`
  display: ${(props) => (props.$isVisible ? 'block' : 'none')};
  margin-top: 20px;
  background-color: white;
  border-radius: 30px;
  height: 400px;
  box-shadow: 0px 0px 7px -3px #bcbcbc;
`;
export function SearchRecommend(props: SearchRecommendProps) {
  return (
    <Container $isVisible={props.isVisible}>
      {/* ... */}
    </Container>
  );
}
export function Search() {
  /* ... */
  
  return (
    <div>
      <SearchForm
        keyword={keyword}
        onChange={handleKeywordChange}
        onFocus={setRecommendVisible}
        onBlur={setRecommendInvisible}
      />
      <SearchRecommend isVisible={isInputFocused} />
    </div>
  );
}

display: none과 조건부 렌더링의 차이점은 DOM 트리에 반영되느냐이다.
전자는 눈에 보이지는 않을 뿐 DOM 트리에 있기 때문에 개발자 도구에서 볼 수 있지만 후자는 렌더링되었을 때만 DOM 트리에서 볼 수 있다.

그럼 어떤 방식이 더 좋을까하는 고민이 생겼고 나름 두 가지 방법에 대한 장단점을 생각해보았다.

  • display: none
    • 장점: 유저 인터랙션에 민감하게 반응해서 자주 모습을 보였다 없어지는 경우, 미리 DOM 트리에 추가해놓은 상태에서 css 속성만 바꾼다면 DOM을 계속 새로 만들고 없애는 것보다는 비용이 덜 든다.
    • 단점: DOM 트리에 존재하기 때문에 렌더링되지 않더라도 필연적으로 DOM 생성을 위한 비용이 든다. 그리고 styled-components를 적용한 상태에서 css 속성을 바꾸기 위한 코드가 조건부 렌더링보다는 더 많다.
  • 조건부 렌더링
    • 장점: 유저 인터랙션에 따라 아예 렌더링되지 않을 가능성이 큰 컴포넌트라면 DOM 트리에도 포함하지 않는 것도 최적화 방법이 될 수 있다. 또한 코드 측면에서 작성할 것이 적고 개인적으로는 더 직관적이다.
    • 단점: 만약 유저 인터랙션에 민감하게 반응해서 자주 모습을 보였다 없어지는 경우라면 계속 DOM을 생성하고 없애야하기 때문에 비용이 더 클 수 있다.

그럼 검색창의 경우는 어떨까? 검색창의 목적을 생각하면 검색어를 입력하고 그에 대한 연관 검색어, 추천 검색어를 보는 것이 필연적이다. 그리고 focus 여부에 따라 결과창이 나타나고 없어지는 것이 빈번하기 때문에 조건부 렌더링보다는 display: none이 더 적합할 것이라 생각한다.

display: none, visibility: hidden, opacity: 0 비교

  • display: none : DOM 트리에 있지만 렌더링 X, 공간 차지 X
  • visibility: hidden : DOM 트리에 있고 렌더링 O, 공간 차지 O, 요소 뒷 부분 클릭 가능
  • opacity: 0 : DOM 트리에 있고 렌더링 O, 공간 차지 O, 요소 뒷 부분 클릭 불가능

유명한 웹 사이트를 참고하는 것도 좋은 방법이라 네이버 검색창을 개발자 도구로 봤는데 display: none으로 결과창을 안 보이게 하고 있었다. 나름대로 추측한 위와 같은 이유가 아닐까 생각하는데 결론은 상황에 맞게 적합한 방식을 사용하는 것이 맞다.

그럼 조건부 렌더링은 어떨 때 사용하면 좋을지도 고민해봤는데 탭 메뉴와 같이 state에 따라 노출되는 컨텐츠가 달라지고 서로 영향을 주지 않는 독립적인 컨텐츠라면 조건부 렌더링이 맞는 것 같다.

profile
될때까지 해보기

0개의 댓글