본문 바로가기
항해99/프로젝트

[TIL-32] 실전 프로젝트 - 검색 기능 구현(useContext로 전역 상태, 검색 쿼리 관리하기)

by junvely 2023. 6. 5.

Today목표 : 06/04일 실전 프로젝트 : 검색 기능 구현 - useContext로 전역 상태(검색 쿼리) 관리 하기

오늘은 내가 맡은 스코프 중 메인 페이지의 검색 기능을 구현하며 사용한 useContext의 기본적인 사용 방법과 리팩토링 과정을 정리해 보려고 한다.

우선 검색 기능 플로우를 살펴보자. 사용자가 header의 검색 아이콘을 클릭하면 토글이 내려와 검색어를 입력하도록 한다. 검색된 결과물은 메인 페이지에 보여져야 한다. 메인 페이지와 header는 Layout 컴포넌트 하위에 위치하고 있다. 

각각 별개의 컴포넌트에서 검색 쿼리에 대한 데이터를 주고 받아야 하기 때문에, 전역적으로 상태를 관리해야 할 필요성이 있다고 생각했다. 이에 지금까지는 클라이언트 전역 상태를 redux를 사용해왔었지만 이번엔 다른 라이브러리 등에 도전해 보고 싶었고 useContext는 라이브러는 아니지만 useContext와 recoil 중에서 먼저 경험해 보고자 시도해 보았다. useContext를 먼저 경험해 보고자 한 것은 아무래도 타 라이브러리들을 사용하기 전에 우선적으로 React에서 제공하는 React hook 중 하나이기 때문에 기본적으로 react hook으로 전역 상태를 관리해 보고 싶었기 때문이었다. 


알게된점,

 

1. useContext 

"리액트 컴포넌트에서 Props가 아닌 또 다른 방식으로 컴포넌트간에 값을 전달하는 방법" 중 하나라고 할 수 있을 것 같다.

context provider를 이용해 props drilling의 가독성과 불편함 등의 문제점을 깔끔하게 해결해 줄 수 있고 provider 내의 어디서든 상태에 접근하거나 업데이트가 가능하게 해준다.

 

useContext – React

The library for web and native user interfaces

react.dev

 

 

2. useContext 사용해 보기 

기본적인 사용 방법

useContext의 기본적인 사용 방법은 다음과 같다.

1. 검색어 query를 관리하는 상태를 상위의 Layout 컴포넌트에서 provider해 준다. 

//1. context 생성 => SearchQueryContext.js에서 따로 관리하였음
const SearchQueryContext = createContext();

//2. 사용부 Layout.jsx 컴포넌트
function Layout() {
  const [searchQuery, setSearchQuery] = useState({
    page: '',
    size: '',
    keyword: '',
    sorting: '인기순',
    district: '',
  });

  const updateSearchQuery = newPayload => {
    setSearchQuery(newPayload);
  };

  const searchPayloadMemoizedValue = useMemo(
    () => ({
      searchQuery,
      updateSearchQuery,
    }),
    [searchQuery, updateSearchQuery],
  );

  return (
  //3. provider value로 주입
    <SearchPostsContext.Provider value={searchPayloadMemoizedValue}>
      <div className={styles.wrap}>

이렇게 하면 Layout 컴포넌트 하위에서 어디서든 SearchQueryContext에 접근 및 사용이 가능하다.

 

 

2. 사용할 컴포넌트에서 value에 접근해 값을 가져오기 

//1. MainPage.jsx
  const { searchQuery, updateSearchQuery } = useContext(SearchQueryContext);
  ...
const { data, isLoading, isError, refetch } = useQuery('mainPosts', () => {
    const result = getMainPosts({
      ...searchQuery, // 현재 searchQuery의 상태로 데이터를 요청하는 로직
    });
    return result;
  });
//2. header의 searhBar.jsx
  const { searchQuery, updateSearchQuery } = useContext(SearchQueryContext);
  ...
  const handleSubmitSearchPost = () => {
    updateSearchPayload({ //검색어로 searchQuery의 상태를 업데이트
      ...searchQuery,
      keyword: input,
      district: location,
    });

이렇게 해서 검색바에서 검색어로 SearchQuery를 업데이트 시키고, mainPage에서는 SearchQuery를 useEffect에서 관찰하고 있다가, 변경될 때 마다 데이터를 다시 요청시켜 데이터를 업데이트시켜 보여준다.

// mainPage.jsx 
useEffect(() => {
    refetch();
    updatePostData();
  }, [SearchQuery]);

 

 

 

3. useContext 리팩토링 하기 => 로직 분리

하지만 이렇게 Layout 컴포넌트에서 SearchQueryContext의 상태와 업데이트 함수들을 관리하니 코드도 너무 난잡해 보이고 분리하고자 하는 리팩토링의 필요성이 절실했다.. 따라서 SearchQueryContext.js에서 관련 상태와 업데이트 함수 로직들을 다같이 관리하게끔 리팩토링 해보았다.

 

1. SearchQueryContext.js에서 SearchQuery상태와 업데이트 함수, 메모이제이션한 value까지 함께 관리해 주고, provider를 리턴하는 컴포넌트를 생성한다. chidren을 인자로 받는 이유는 provider로 감싸서 children으로 렌더링 시켜주기 위해서다.

import { createContext, useMemo, useState } from 'react';

const SearchQueryContext = createContext();

function SearchQueryProvider({ children }) {
  const [searchQuery, setSearchQuery] = useState({
    page: '',
    size: '',
    keyword: '',
    sorting: '인기순',
    district: '',
  });

  const updateSearchQuery = newPayload => {
    setSearchQuery(newPayload);
  };

  const searchQueryValue = useMemo(
    () => ({
      searchQuery,
      updateSearchQuery,
    }),
    [searchQuery, updateSearchQuery],
  );

  return (
    <SearchQueryContext.Provider value={searchQueryValue}>
      {children}
    </SearchQueryContext.Provider>
  );
}

export { SearchQueryContext, SearchQueryProvider };

searchQueryContext와 SearchQueryProvider 컴포넌트를 같이 export해준다.

 

2. Layout 컴포넌트에서는 SearchQueryProvider 컴포넌트를 렌더링하고 children으로 하위 컴포넌트를 넘겨준다.

function Layout() {
  return (
    <SearchQueryProvider>
      <div className={styles.wrap}>
       ...
      </div>
    </SearchQueryProvider>
  );
}

 

3. 사용부인 MainPage와 header의 searchBar에서는 리팩토링 전과 동일하게 searchQuery와 상태 업데이트 함수를 사용 가능하다.

//mainPage와 header의 searhBar.jsx에서 searchQuery와 업데이트 함수를 동일하게 사용 가능
  const { searchQuery, updateSearchQuery } = useContext(SearchQueryContext);

 

 

 

목표 달성 여부,

1. 검색 기능 구현 완료 ✅

2. useEffect 리팩토링 완료 

 

느낀 점,

기존에 전역상태 관리로 Redux를 사용했었다. Redux는 클라이언트의 전역 상태를 관리하기 위한 라이브러리고, useContext는 단지 전역 상태 관리를 위한 것 만은 아니다. Context는 전역 상태 관리를 할 수 있는 수단일 뿐이고, 상태 관리 라이브러리는 상태 관리를 더욱 편하고, 효율적으로 할 수 있게 해주는 기능들을 제공해주는 도구라고 할 수 있다. useContext로 전역상태를 관리하는 것이 마냥 그렇게 쉽지만은..? 않은 것 같다. 리액트의 개념도 더해지기 때문인가.. 처음 배우는 것이라 조금 복잡하다는 생각도 들었다. 그렇다고 Redux도 사실 처음 배울 땐 많이 복잡했다... 전역 상태 관리를 위해서 사용하기에 사실 둘 다 간편하다는 생각은 안들었다. 사실 이렇게까지 해야 한다니.. 더 간편한 방법은 없나? 라는 생각도 든다. 다음 리팩토링 주간에는 Recoil을 사용해서 리팩토링 해봐야 할 것 같다. 세가지를 모두 사용해 봐야 각각의 장단점이 좀 보이고 선호하는 라이브러리가 생길 수도 있을 것 같다!