굿즈 스토어 프로젝트 07 - 상품 목록 페이지, 데이터 정렬 기능.

이유승·2023년 7월 20일
0

상품의 데이터를 리스트 형태로 가져와서 화면에 렌더링하는 것은 이전 포스트에서 다루어보았다. 상품 목록 페이지에서는 이 기능에 검색어를 통해서 제품을 검색하는 기능과, 특정 카테고리에 속하는 제품만을 조회하는 기능, 그리고 특정 조건대로 데이터를 정렬하는 기능을 더해보았다.



1. 기본 구조는 같다. 다만..

파이어베이스의 query 함수를 사용하는 것은 동일하다. 단지 데이터를 정렬하는 방식을 설정할 수 있는 orderBy 함수와 특정 필드를 기준으로 특정 데이터만을 조회할 수 있는 where 함수가 추가되는 것 뿐이다.

사용자가 특정 키워드를 입력해서 검색하는 방식과, 특정 카테고리의 상품만 조회하는 방식 모두 where 함수를 사용한다.

queryRef = query( (...) where('productDisclosure', '==', true), limit(itemPerPage));

위 코드를 예시로 설명하는 where 함수의 문법은 다음과 같다.

  • 첫 번째 인자는 조건을 검사할 필드의 이름. ('productDisclosure')

  • 두 번째 인자는 비교 연산자. ('==')

  • 세 번째 인자는 비교할 값. (true)

따라서 위 코드의 데이터 검색 조건은 다음과 같다.
-> productDisclosure 필드의 값이 true인 요소만 가지고 와라.

따라서 where 함수의 인자를 상황에 맞게 사용하기만 하면, 키워드 검색과 카테고리 검색 모두 구현할 수 있다.

// 키워드 검색
// 상품의 name이 keyword와 동일한 요소만 가지고와라.
queryRef = query( (...) where('name', '==', keyword));

// 카테고리 검색
// 상품의 mainCategory가 keyword와 동일한 요소만 가지고와라.
queryRef = query( (...) where('mainCategory', '==', keyword));



2. 기능의 시작은 스토어 상단 메뉴 바.

원래 페이지 상단에 위치하는 메뉴바는 동일한 컴포넌트를 모든 페이지에 적용할 생각이었다. 그런데 제품 검색 기능을 집어넣을 장소를 찾다가 스토어 어디에서도 바로 접근이 가능한 지점이 메뉴바 밖에 없었고, 기능을 추가하다보니 기존 메뉴바를 그대로 쓰기가 어려웠기에 스토어 전용 상단 메뉴바를 따로 구현하였다.

우선 상품 목록 페이지로 넘어가는 방식. 검색 창에 키워드를 입력하거나, 카테고리 버튼을 클릭하면 상품 목록 페이지로 이동한다. 동시에 필요한 변수들을 URL Param으로 넘겨 기능 구현에 사용하도록 하였다.

const onSearch = (keyword) => {
    if (!searchKeyword) {
        alert('검색어를 입력해주세요.');
        return;
    };
    navigate(`/store/productlist/keywordSearch/${searchKeyword}`);
    setSearchKeyword('');
    setIsCategoryShow(false);
};

const onCategorySearch = (keyword) => {
    navigate(`/store/productlist/categorySearch/${keyword}`);
    setSearchKeyword('');
    setIsCategoryShow(false);
};

그리고 상품 목록 페이지에서는 useParam Hook을 이용하여 Param들을 받아와서 사용한다.

const { searchtype } = useParams(); // 일반 검색시에는 keywordSearch, 카테고리 검색은 category.
const { keyword } = useParams(); // 일반 검색시에는 검색값, 카테고리 검색은 카테고리 이름.

우선 상품 목록을 가져오는 방식이 키워드 검색인지, 카테고리 검색인지를 searchtype 변수를 통해 구분한다. 또 DB에서 검색 기준으로 삼을 값이 필요한데 이는 keyword 변수를 통해 구분한다. keyword 변수는 키워드 검색 시에는 검색한 키워드가, 카테고리 검색시에는 카테고리의 명칭의 이름이 대입된다.

또한 정렬 기능의 경우, 버튼을 클릭할 때 sortCondition 변수의 값이 변화하도록 구현하였다.

<ListType>
	<p onClick={() => setSortCondition('인기도순')}>인기도순</p>
	<p onClick={() => setSortCondition('낮은 가격순')}>낮은 가격순</p>
	<p onClick={() => setSortCondition('높은 가격순')}>높은 가격순</p>
	<p onClick={() => setSortCondition('리뷰 많은순')}>리뷰 많은순</p>
	<p onClick={() => setSortCondition('등록일 순')}>등록일 순</p>
</ListType>

기능 동작에 필요한 변수들은 얼추 이렇고, 이제 남은 것은 기능이 어떻게 동작하게 만드느냐이다. 고민을 하다가 useEffect Hook을 사용해보기로 했다.

useEffect(() => {
    // 키워드 검색 기능.
    if (searchtype === 'keywordSearch') {
        dispatch(GetSearchProductList('keywordSearch', 10, keyword, sortCondition));
    }
    // 카테고리 검색 기능.
    else if (searchtype === 'categorySearch') {
        dispatch(GetSearchProductList('categorySearch', 10, keyword, sortCondition));
    }
    // eslint-disable-next-line
}, [searchtype, keyword, sortCondition]);

동작 원리는 다음과 같다. useEffect Hook과 디펜던시를 이용하여 searchtype, keyword, sortCondition 중 어느 하나의 값이라도 변화하였을 때 검색 기능이 다시 호출된다.

상단 메뉴바에서 url을 이동시키는 구현 방식을 가지고 있는 키워드 검색이나 카테고리 검색은 무조건 컴포넌트 리렌더링이 일어나지만, 목록 정렬의 경우 정렬 기준이 바뀌었을 때 이를 감지하여 검색 기능이 다시 호출되도록 구현하였다.

따라서 sortCondition가 변화하였을 때마다 useEffect Hook을 통해 검색 기능이 다시 동작하고 Redux Store의 값이 변화하는 것을 다른 useEffect Hook으로 감지하여 화면에 렌더링 할 데이터들을 갱신되어 화면에 렌더링 된다.



3. 어렵네..

백엔드쪽 코드를 어떻게 구현해야 할까. 어떤 방식으로 검색이 이루어지는지에 따라서 다른 queryRef를 사용해주어야 한다.

let queryRef = '';

if (listCallType === 'keywordSearch') {
	if (sortCondition === '높은 가격순') {
		queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'desc'), where('name', '==', keyword), where('productDisclosure', '==', true), limit(itemPerPage));
	}
    else {
    	queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'asc'), where('name', '==', keyword), where('productDisclosure', '==', true), limit(itemPerPage));
    };
}
else if (listCallType === 'categorySearch') {
	if (keyword === '전체상품') {
    	if (sortCondition === '높은 가격순') {
        	queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'desc'), where('productDisclosure', '==', true), limit(itemPerPage));
        }
        else {
        	queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'asc'), where('productDisclosure', '==', true), limit(itemPerPage));
        };
    }
    else {
    	if (sortCondition === '높은 가격순') {
        	queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'desc'), where('mainCategory', '==', keyword), where('productDisclosure', '==', true), limit(itemPerPage));
        }
        else {
        	queryRef = query(storeCollectionRef, orderBy(sortConditionEng, 'asc'), where('mainCategory', '==', keyword), where('productDisclosure', '==', true), limit(itemPerPage));
        };
    };
};

머리를 엄청나게 굴린 끝에 위와 같은 구성을 구현하여 기능을 동작하게 하는데 성공했다.

우선 가장 처음으로는 키워드 검색과 카테고리 검색을 구분한다. 키워드 검색은 where 함수에서 제품명을 필드값으로 사용해야 하고, 카테고리 검색은 필드값으로 카테고리 분류를 사용해야 하기 때문이다.

키워드 검색은 다음 순서로 정렬 기준에 따라서 분기가 또 나누어진다. 정렬 기능은 기본적으로 오름차순으로 데이터를 반환하도록 되어있다. 그런데 높은 가격순의 경우 데이터를 내림차순으로 정렬해야하기 때문에 orderBy 함수의 인자를 다르게 사용해야 하기 때문이다.

카테고리 검색도 정렬쪽은 동일하게 분기를 나누어야 하지만, 그보다 먼저 해야하는 일이 하나 있다. 상품 목록 페이지에서는 키워드 검색, 카테고리 검색 이외에도 수행해야할 기능이 하나 더 있다. 바로 전체 상품 검색. 이 기능을 처리하는 분기는 아예 밖으로 따로 빼도 상관없긴 하지만 카테고리 버튼 쪽에 위치해 있으므로 같은 곳에서 처리하였다.



4. 그리고 하나 더..

마지막으로 구현해야하는 것은 서브 카테고리 검색 기능.

이번 프로젝트에서 상품 DB는 제품별 분류를 대분류와 소분류로 나누고 있다. 상품의 종류가 다양한데 이걸 모두 개별 분류로 나누기에는 분기가 너무 많아졌기에 큰 틀에서 대분류를 한번 잡아주었다.

지금까지 구현한 카테고리 검색기능은 대분류 단위에서만 동작하는 기능이었고, 이제는 소분류 단위에서 동작하는 검색 기능을 구현해야 한다.

사실 구현 자체는 별게 아니다. 소분류 검색은 단지 대분류 검색에서 where 함수의 인자만 다르게 적용하면 되기 때문이다. 다만 다른 곳에서 문제가 발생했다..

이미 키워드, 카테고리, 제품 정렬 기능이 완성되어 있는 상황에서 소분류 검색 기능을 추가하려니 전체 구조를 뜯어서 수정하지 않으면 구현이 어려웠기 때문! 기존에 존재하는 기능 구조는 키워드, 카테고리, 제품 정렬의 3가지 방법만 구분할 수 있었다.. 이리저리 머리를 굴려보다가 도저히 뾰족한 수가 보이지 않아서 한심하지만 소분류 검색을 위한 함수를 따로 분리하였다..

{keyword === '인형, 피규어' &&
	<TypeButton>
		<button onClick={() => subCategorySearch('인형')}>인형</button>
		<button onClick={() => subCategorySearch('피규어')}>피규어</button>
	</TypeButton>
}

{keyword === '문구잡화' &&
	<TypeButton>
		<button onClick={() => subCategorySearch('마우스패드')}>마우스패드</button>
		<button onClick={() => subCategorySearch('아크릴스탠드')}>아크릴스탠드</button>
		<button onClick={() => subCategorySearch('열쇠고리')}>열쇠고리</button>
	</TypeButton>
}

(...)

우선 프론트 UI. 대분류에 따라서 적절한 소분류 버튼이 출력되고 이를 클릭하면 소분류 검색 기능이 동작하는 함수를 호출한다.

const subCategorySearch = (keyword) => {
    dispatch(GetSearchSubCategoryProductList('categorySearch', 10, keyword, sortCondition));
};

프론트쪽 함수는 Action Creator 함수를 필요한 인자를 넣어 호출한다. 앞서 구현한 백엔드 코드에서 분기를 또 나누면 되긴 하지만, 조건문이 몇 번이고 중첩되니 가독성이 심각하게 떨어지고 유지보수 효율도 너무 안좋아져서 소분류 검색 기능은 함수를 분리하여 구현하였다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 더 효율적이고 보기 좋은 코드 작성 방법 구상. 처음부터 모든 기능을 상정하여 코드를 짜고, 조건문이나 코드 중복을 최소화하는 코딩 방법을 구상할 필요가 있다. 조건문이 기본이 2중첩이고 상황에 따라서는 3중첩까지 이루어지니 에러가 발생하거나 기능에 이상이 생겼을 때 이를 보수하는게 정말 어렵다.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글