사용자가 원하는 기준으로 상품을 정렬해서 볼 수 있는 기능은 쇼핑몰의 사용자 경험을 크게 향상시킵니다.

오늘은 정렬(Sort) 기능(상품 가격을 기준으로 내림차순, 오름차순) 구현에 필요한 JavaScript 핵심 문법을 알아보겠습니다.

1. 배열의 sort() 메서드 기본

sort() 메서드는 배열의 요소를 정렬합니다. 기본적으로 문자열 순서로 정렬됩니다.

기본 문법

array.sort((a, b) => {
  // a가 b보다 앞에 오려면 음수 반환
  // a가 b 뒤에 오려면 양수 반환
  // 순서를 바꾸지 않으려면 0 반환
});

주의사항

const numbers = [1, 30, 4, 21, 100000];

// 잘못된 사용 - 문자열로 정렬됨
numbers.sort();
console.log(numbers); // [1, 100000, 21, 30, 4]

// 올바른 사용 - 숫자로 정렬
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 4, 21, 30, 100000]

2. 숫자 정렬

오름차순 정렬

const prices = [100, 50, 200, 25, 150];

// 가격 낮은 순
const ascending = [...prices].sort((a, b) => a - b);
console.log(ascending); // [25, 50, 100, 150, 200]

내림차순 정렬

// 가격 높은 순
const descending = [...prices].sort((a, b) => b - a);
console.log(descending); // [200, 150, 100, 50, 25]

왜 스프레드 연산자를?

원본 배열을 유지하고, 순서를 변경한 값을 새로운 배열에 할당하기 위해서 사용합니다.

// sort()는 원본 배열을 변경합니다!
const original = [3, 1, 2];
original.sort(); // [1, 2, 3]
console.log(original); // [1, 2, 3] - 원본이 변경됨!

// 원본을 보존하려면 복사본에 정렬
const copy = [...original].sort();

3. 객체 배열 정렬

쇼핑몰 상품 데이터는 대부분 객체 배열입니다.

가격으로 정렬

const products = [
  { id: 1, name: '노트북', price: 1000 },
  { id: 2, name: '마우스', price: 30 },
  { id: 3, name: '키보드', price: 70 },
  { id: 4, name: '모니터', price: 300 }
];

// 가격 낮은 순
const byPriceAsc = [...products].sort((a, b) => a.price - b.price);

// 가격 높은 순
const byPriceDesc = [...products].sort((a, b) => b.price - a.price);

이름으로 정렬

// 이름 가나다순
const byNameAsc = [...products].sort((a, b) => 
  a.name.localeCompare(b.name)
);

// 이름 역순
const byNameDesc = [...products].sort((a, b) => 
  b.name.localeCompare(a.name)
);

4. React에서 정렬 구현하기

import { useState, useEffect } from 'react';

function ProductSort() {
  const [products, setProducts] = useState([]);
  const [sortOption, setSortOption] = useState('default');

  // 상품 데이터 가져오기
  useEffect(() => {
    fetch('https://fakestoreapi.com/products')
      .then(res => res.json())
      .then(data => setProducts(data));
  }, []);

  // 정렬된 상품 계산
  const getSortedProducts = () => {
    const sorted = [...products]; // 원본 보존

    switch (sortOption) {
      case 'price-asc':
        return sorted.sort((a, b) => a.price - b.price);
      
      case 'price-desc':
        return sorted.sort((a, b) => b.price - a.price);
      
      case 'name-asc':
        return sorted.sort((a, b) => a.title.localeCompare(b.title));
      
      case 'name-desc':
        return sorted.sort((a, b) => b.title.localeCompare(a.title));
      
      case 'rating':
        return sorted.sort((a, b) => b.rating.rate - a.rating.rate);
      
      default:
        return sorted; // 기본 순서
    }
  };

  const sortedProducts = getSortedProducts();

  return (
  {sortedProducts.map(product => ( ))}
  );
}

 

5. localeCompare()로 문자열 비교

localeCompare()는 언어 규칙에 맞게 문자열을 비교합니다.

// 한글 정렬
const names = ['홍길동', '김철수', '이영희'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'ko'));
console.log(sorted); // ['김철수', '이영희', '홍길동']

// 영문 정렬 (대소문자 무시)
const products = ['Apple', 'banana', 'Cherry'];
const sorted2 = products.sort((a, b) => 
  a.localeCompare(b, 'en', { sensitivity: 'base' })
);
console.log(sorted2); // ['Apple', 'banana', 'Cherry']

6. 복잡한 정렬 조건

여러 조건으로 정렬

const products = [
  { name: '노트북', category: 'electronics', price: 1000 },
  { name: '마우스', category: 'electronics', price: 30 },
  { name: '셔츠', category: 'clothing', price: 50 },
  { name: '청바지', category: 'clothing', price: 80 }
];

// 카테고리별로 먼저 정렬, 같은 카테고리 내에서는 가격순
const sorted = [...products].sort((a, b) => {
  // 1순위: 카테고리
  const categoryCompare = a.category.localeCompare(b.category);
  if (categoryCompare !== 0) {
    return categoryCompare;
  }
  
  // 2순위: 가격
  return a.price - b.price;
});

console.log(sorted);
// clothing 카테고리가 먼저 나오고, 그 안에서 가격순

날짜로 정렬

const reviews = [
  { text: '좋아요', date: new Date('2024-01-15') },
  { text: '별로에요', date: new Date('2024-01-10') },
  { text: '최고!', date: new Date('2024-01-20') }
];

// 최신순
const newest = [...reviews].sort((a, b) => b.date - a.date);

// 오래된 순
const oldest = [...reviews].sort((a, b) => a.date - b.date);

7. 성능 최적화: useMemo 활용

정렬은 비용이 큰 작업입니다. useMemo로 최적화하세요.

import { useMemo } from 'react';

function OptimizedSort() {
  const [products, setProducts] = useState([]);
  const [sortOption, setSortOption] = useState('default');

  // sortOption이나 products가 변경될 때만 재계산
  const sortedProducts = useMemo(() => {
    console.log('정렬 계산 실행');
    const sorted = [...products];

    switch (sortOption) {
      case 'price-asc':
        return sorted.sort((a, b) => a.price - b.price);
      case 'price-desc':
        return sorted.sort((a, b) => b.price - a.price);
      default:
        return sorted;
    }
  }, [products, sortOption]);

  return <ProductList products={sortedProducts} />;
}

8. 버튼으로 정렬 토글

같은 버튼을 누르면 오름차순/내림차순이 토글되는 UX입니다.

function SortToggle() {
  const [products, setProducts] = useState([]);
  const [sortConfig, setSortConfig] = useState({
    key: null,
    direction: 'asc'
  });

  const handleSort = (key) => {
    let direction = 'asc';
    
    // 같은 키를 다시 클릭하면 방향 전환
    if (sortConfig.key === key && sortConfig.direction === 'asc') {
      direction = 'desc';
    }
    
    setSortConfig({ key, direction });
  };

  const sortedProducts = useMemo(() => {
    const sorted = [...products];
    
    if (sortConfig.key) {
      sorted.sort((a, b) => {
        const aValue = a[sortConfig.key];
        const bValue = b[sortConfig.key];
        
        if (typeof aValue === 'string') {
          return sortConfig.direction === 'asc'
            ? aValue.localeCompare(bValue)
            : bValue.localeCompare(aValue);
        }
        
        return sortConfig.direction === 'asc'
          ? aValue - bValue
          : bValue - aValue;
      });
    }
    
    return sorted;
  }, [products, sortConfig]);

  return (
    <div>
      <button onClick={() => handleSort('price')}>
        가격순 {sortConfig.key === 'price' && (
          sortConfig.direction === 'asc' ? '▲' : '▼'
        )}
      </button>
      
      <button onClick={() => handleSort('title')}>
        이름순 {sortConfig.key === 'title' && (
          sortConfig.direction === 'asc' ? '▲' : '▼'
        )}
      </button>

      <ProductList products={sortedProducts} />
    </div>
  );
}

9. 실전 정렬 함수 모음

재사용 가능한 정렬 함수들입니다.

// 정렬 유틸리티 함수
const sortUtils = {
  // 숫자 오름차순
  numberAsc: (a, b, key) => a[key] - b[key],
  
  // 숫자 내림차순
  numberDesc: (a, b, key) => b[key] - a[key],
  
  // 문자열 오름차순
  stringAsc: (a, b, key) => a[key].localeCompare(b[key]),
  
  // 문자열 내림차순
  stringDesc: (a, b, key) => b[key].localeCompare(a[key]),
  
  // 날짜 최신순
  dateDesc: (a, b, key) => new Date(b[key]) - new Date(a[key]),
  
  // 날짜 오래된순
  dateAsc: (a, b, key) => new Date(a[key]) - new Date(b[key])
};

// 사용 예시
const sortedByPrice = [...products].sort((a, b) => 
  sortUtils.numberAsc(a, b, 'price')
);

const sortedByName = [...products].sort((a, b) => 
  sortUtils.stringAsc(a, b, 'name')
);

10. 필터링과 정렬 함께 사용하기

function FilterAndSort() {
  const [products, setProducts] = useState([]);
  const [category, setCategory] = useState('all');
  const [sortOption, setSortOption] = useState('default');

  const processedProducts = useMemo(() => {
    // 1. 필터링
    let filtered = products;
    if (category !== 'all') {
      filtered = products.filter(p => p.category === category);
    }

    // 2. 정렬
    const sorted = [...filtered];
    switch (sortOption) {
      case 'price-asc':
        return sorted.sort((a, b) => a.price - b.price);
      case 'price-desc':
        return sorted.sort((a, b) => b.price - a.price);
      default:
        return sorted;
    }
  }, [products, category, sortOption]);

  return (
    <div>
      {/* 카테고리 필터 */}
      <select onChange={(e) => setCategory(e.target.value)}>
        <option value="all">전체</option>
        <option value="electronics">전자제품</option>
        <option value="clothing">의류</option>
      </select>

      {/* 정렬 옵션 */}
      <select onChange={(e) => setSortOption(e.target.value)}>
        <option value="default">기본순</option>
        <option value="price-asc">가격 낮은순</option>
        <option value="price-desc">가격 높은순</option>
      </select>

      {/* 결과 */}
      <div>총 {processedProducts.length}개의 상품</div>
      <ProductList products={processedProducts} />
    </div>
  );
}

11. 실전 팁

1. 빈 배열 체크

if (!products || products.length === 0) {
  return <div>상품이 없습니다</div>;
}

2. null/undefined 처리

const sorted = [...products].sort((a, b) => {
  // price가 없는 상품은 맨 뒤로
  if (!a.price) return 1;
  if (!b.price) return -1;
  return a.price - b.price;
});

3. 대소문자 구분 없이 정렬

const sorted = [...products].sort((a, b) => 
  a.name.toLowerCase().localeCompare(b.name.toLowerCase())
);

4. 안정적인 정렬 (원본 순서 유지)

// 같은 값일 때 원본 순서 유지
const sorted = [...products].sort((a, b) => {
  const priceCompare = a.price - b.price;
  if (priceCompare !== 0) return priceCompare;
  
  // 가격이 같으면 원본 인덱스 순서 유지
  return products.indexOf(a) - products.indexOf(b);
});

마무리

정렬 기능 구현에 필요한 핵심 JavaScript 문법을 정리하면:

  • sort(): 배열 정렬의 기본 메서드
  • 비교 함수: (a, b) => a - b 패턴 이해하기
  • localeCompare(): 문자열 자연스러운 정렬
  • 스프레드 연산자: 원본 배열 보존
  • useMemo: 성능 최적화
  • 다중 조건 정렬: 우선순위 정렬 구현

이러한 개념들을 활용하면 사용자 친화적인 정렬 기능을 자유롭게 구현할 수 있습니다!


더 자세한 내용이 궁금하시다면?
정렬부터 고급 필터링, 검색 기능까지! 실무에서 바로 사용할 수 있는 쇼핑몰 구축 노하우가 궁금하시다면 인프런 강의를 확인해주세요!

반응형

쇼핑몰 

상품이 많아지면 한 페이지에 모든 상품을 보여주기 어렵습니다. 페이지네이션(Pagination)은 데이터를 여러 페이지로 나누어 보여주는 필수 기능입니다. 오늘은 페이지네이션 구현에 필요한 JavaScript 핵심 문법을 알아보겠습니다.

1. 배열의 slice() 메서드

slice() 메서드는 배열의 특정 부분을 잘라내 새로운 배열을 만듭니다. 페이지네이션의 핵심 메서드입니다.

기본 문법

array.slice(startIndex, endIndex)
// startIndex: 시작 위치 (포함)
// endIndex: 끝 위치 (미포함)

실전 예제

const products = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 처음 3개 가져오기
const page1 = products.slice(0, 3);
console.log(page1); // [1, 2, 3]

// 3번째부터 3개 가져오기
const page2 = products.slice(3, 6);
console.log(page2); // [4, 5, 6]

// 6번째부터 3개 가져오기
const page3 = products.slice(6, 9);
console.log(page3); // [7, 8, 9]

2. 페이지네이션 기본 계산식

페이지네이션을 구현하려면 몇 가지 계산이 필요합니다.

const totalItems = 100;      // 전체 아이템 수
const itemsPerPage = 10;     // 페이지당 아이템 수
const currentPage = 1;       // 현재 페이지 (1부터 시작)

// 전체 페이지 수 계산
const totalPages = Math.ceil(totalItems / itemsPerPage);
// Math.ceil(100 / 10) = 10페이지

// 현재 페이지의 시작 인덱스
const startIndex = (currentPage - 1) * itemsPerPage;
// (1 - 1) * 10 = 0

// 현재 페이지의 끝 인덱스
const endIndex = startIndex + itemsPerPage;
// 0 + 10 = 10

// 현재 페이지의 아이템
const currentItems = allItems.slice(startIndex, endIndex);

3. React로 페이지네이션 구현하기

import { useState, useEffect } from 'react';

function Pagination() {
  const [products, setProducts] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const itemsPerPage = 8;

  // 상품 데이터 가져오기
  useEffect(() => {
    fetch('https://fakestoreapi.com/products')
      .then(res => res.json())
      .then(data => setProducts(data));
  }, []);

  // 전체 페이지 수 계산
  const totalPages = Math.ceil(products.length / itemsPerPage);

  // 현재 페이지의 상품들
  const indexOfLastItem = currentPage * itemsPerPage;
  const indexOfFirstItem = indexOfLastItem - itemsPerPage;
  const currentProducts = products.slice(indexOfFirstItem, indexOfLastItem);

  // 페이지 변경 핸들러
  const handlePageChange = (pageNumber) => {
    setCurrentPage(pageNumber);
    window.scrollTo(0, 0); // 페이지 상단으로 스크롤
  };

  return (  
  );
}
 

4. Math 객체 활용

페이지네이션에서 자주 사용되는 Math 메서드들입니다.

Math.ceil() - 올림

// 전체 페이지 수는 항상 올림
const totalPages = Math.ceil(23 / 10);
console.log(totalPages); // 3

Math.floor() - 내림

const pageNumber = Math.floor(15 / 10);
console.log(pageNumber); // 1

Math.min() / Math.max() - 최소/최대값

// 페이지 범위 제한
const safePage = Math.max(1, Math.min(requestedPage, totalPages));

5. Array.from()으로 배열 생성

페이지 번호 버튼을 만들 때 유용합니다.

// 1부터 5까지의 배열 만들기
const pages = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(pages); // [1, 2, 3, 4, 5]

// 실전 활용
const totalPages = 10;
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);

다른 방법들

// 방법 1: Array(n).fill()
const pages1 = Array(5).fill(0).map((_, i) => i + 1);

// 방법 2: [...Array(n).keys()]
const pages2 = [...Array(5).keys()].map(i => i + 1);

// 방법 3: 스프레드와 map
const pages3 = [...Array(5)].map((_, i) => i + 1);

6. 제한된 페이지 버튼 표시

모든 페이지를 보여주면 버튼이 너무 많아집니다. 일부만 표시하는 로직입니다.

function PaginationButtons({ currentPage, totalPages, onPageChange }) {
  const getPageNumbers = () => {
    const pages = [];
    const maxButtons = 5; // 최대 표시할 버튼 수
    
    // 시작 페이지 계산
    let startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2));
    
    // 끝 페이지 계산
    let endPage = Math.min(totalPages, startPage + maxButtons - 1);
    
    // 끝에서 버튼이 부족하면 시작 조정
    if (endPage - startPage < maxButtons - 1) {
      startPage = Math.max(1, endPage - maxButtons + 1);
    }
    
    // 페이지 번호 배열 생성
    for (let i = startPage; i <= endPage; i++) {
      pages.push(i);
    }
    
    return pages;
  };

  return (
    <div className="pagination">
      {/* 첫 페이지 */}
      {currentPage > 1 && (
        <button onClick={() => onPageChange(1)}>«</button>
      )}
      
      {/* 이전 */}
      <button 
        onClick={() => onPageChange(currentPage - 1)}
        disabled={currentPage === 1}
      >
        ‹
      </button>

      {/* 페이지 번호들 */}
      {getPageNumbers().map(page => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={currentPage === page ? 'active' : ''}
        >
          {page}
        </button>
      ))}

      {/* 다음 */}
      <button 
        onClick={() => onPageChange(currentPage + 1)}
        disabled={currentPage === totalPages}
      >
        ›
      </button>

      {/* 마지막 페이지 */}
      {currentPage < totalPages && (
        <button onClick={() => onPageChange(totalPages)}>»</button>
      )}
    </div>
  );
}

7. 페이지 정보 표시

사용자에게 현재 위치를 알려주는 정보를 표시합니다.

function PageInfo({ currentPage, totalPages, totalItems, itemsPerPage }) {
  const startItem = (currentPage - 1) * itemsPerPage + 1;
  const endItem = Math.min(currentPage * itemsPerPage, totalItems);

  return (
    <div className="page-info">
      <p>
        전체 {totalItems}개 중 {startItem}-{endItem} 표시
      </p>
      <p>
        페이지 {currentPage} / {totalPages}
      </p>
    </div>
  );
}

8. 페이지당 아이템 수 변경

사용자가 한 페이지에 표시할 상품 수를 선택할 수 있게 합니다.

function ProductListWithOptions() {
  const [currentPage, setCurrentPage] = useState(1);
  const [itemsPerPage, setItemsPerPage] = useState(10);

  const handleItemsPerPageChange = (e) => {
    setItemsPerPage(Number(e.target.value));
    setCurrentPage(1); // 첫 페이지로 리셋
  };

  return (
    <div>
      {/* 페이지당 아이템 수 선택 */}
      <select value={itemsPerPage} onChange={handleItemsPerPageChange}>
        <option value={5}>5개씩 보기</option>
        <option value={10}>10개씩 보기</option>
        <option value={20}>20개씩 보기</option>
        <option value={50}>50개씩 보기</option>
      </select>

      {/* 상품 목록 및 페이지네이션 */}
    </div>
  );
}

9. URL 쿼리 파라미터와 연동

페이지 새로고침해도 현재 페이지를 유지하는 방법입니다.

import { useSearchParams } from 'react-router-dom';

function PaginationWithURL() {
  const [searchParams, setSearchParams] = useSearchParams();
  const currentPage = Number(searchParams.get('page')) || 1;

  const handlePageChange = (page) => {
    setSearchParams({ page: page.toString() });
  };

  // URL: /products?page=3
  return <Pagination currentPage={currentPage} onPageChange={handlePageChange} />;
}

10. 무한 스크롤 구현

페이지네이션 대신 무한 스크롤을 원한다면:

function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    loadMoreItems();
  }, [page]);

  const loadMoreItems = async () => {
    setLoading(true);
    const response = await fetch(`/api/products?page=${page}&limit=10`);
    const newItems = await response.json();
    setItems(prev => [...prev, ...newItems]);
    setLoading(false);
  };

  const handleScroll = () => {
    const bottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 100;
    if (bottom && !loading) {
      setPage(prev => prev + 1);
    }
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [loading]);

  return (
    <div>
      {items.map(item => <ProductCard key={item.id} product={item} />)}
      {loading && <div>로딩 중...</div>}
    </div>
  );
}

실전 팁

1. 페이지 범위 검증

const safePage = Math.max(1, Math.min(requestedPage, totalPages));
setCurrentPage(safePage);

2. 빈 페이지 처리

if (currentProducts.length === 0) {
  return <div>표시할 상품이 없습니다.</div>;
}

3. 로딩 상태 관리

const [loading, setLoading] = useState(false);

const handlePageChange = async (page) => {
  setLoading(true);
  setCurrentPage(page);
  // 데이터 로드 후
  setLoading(false);
};

마무리

페이지네이션 구현에 필요한 핵심 JavaScript 문법을 정리하면:

  • slice(): 배열의 특정 부분 추출
  • Math.ceil(): 전체 페이지 수 계산
  • Array.from(): 페이지 번호 배열 생성
  • 인덱스 계산: startIndex, endIndex 공식
  • 조건부 렌더링: disabled 속성 활용

이러한 개념들을 이해하면 다양한 형태의 페이지네이션을 자유롭게 구현할 수 있습니다!


더 자세한 내용이 궁금하시다면?
페이지네이션부터 무한 스크롤, 그리고 성능 최적화까지! 실무에 바로 적용할 수 있는 방법이 궁금하시다면 인프런 강의를 확인해주세요!

반응형

쇼핑몰에서 사용자가 원하는 카테고리의 상품만 골라서 볼 수 있는 필터링 기능은 필수입니다.

오늘은 카테고리 필터링을 구현하는 데 필요한 JavaScript 핵심 문법들을 알아보겠습니다.

1. 배열의 filter() 메서드

filter() 메서드는 배열에서 특정 조건을 만족하는 요소들만 걸러내는 가장 기본적인 메서드입니다.

기본 문법

 
javascript
const newArray = array.filter(callback(element, index, array));

실전 예제

 
 
javascript
const products = [
  { id: 1, name: '노트북', category: 'electronics', price: 1000 },
  { id: 2, name: '셔츠', category: 'clothing', price: 50 },
  { id: 3, name: '스마트폰', category: 'electronics', price: 800 },
  { id: 4, name: '청바지', category: 'clothing', price: 80 }
];

// electronics 카테고리만 필터링
const electronics = products.filter(product => {
  return product.category === 'electronics';
});

console.log(electronics);
// [{ id: 1, name: '노트북', ... }, { id: 3, name: '스마트폰', ... }]

화살표 함수로 간결하게

상품 목록에서 카테고리 속성의 값이 electronics와 같은 상품을 걸러서(filter) 새 배열을 생성합니다.
 
javascript
// 축약형
const electronics = products.filter(p => p.category === 'electronics');

2. 배열의 map() 메서드

map() 메서드는 배열의 각 요소를 변환하여 새로운 배열을 만듭니다.

카테고리 목록 추출하기

javascript
const products = [
  { name: '노트북', category: 'electronics' },
  { name: '셔츠', category: 'clothing' },
  { name: '스마트폰', category: 'electronics' }
];

// 모든 카테고리 추출
const allCategories = products.map(product => product.category);
console.log(allCategories);
// ['electronics', 'clothing', 'electronics']

3. Set으로 중복 제거하기

카테고리 목록에서 중복을 제거할 때 Set 객체를 활용합니다.

javascript
const categories = ['electronics', 'clothing', 'electronics', 'clothing'];

// Set으로 중복 제거
const uniqueCategories = [...new Set(categories)];
console.log(uniqueCategories);
// ['electronics', 'clothing']

실전 활용

javascript
// 상품 배열에서 고유한 카테고리만 추출
const uniqueCategories = [...new Set(products.map(p => p.category))];

4. 스프레드 연산자 (...)

스프레드 연산자는 배열이나 객체를 펼쳐서 사용할 때 활용합니다.

javascript
// 배열 결합
const categories = ['all', ...uniqueCategories];

// Set을 배열로 변환
const array = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

5. 조건부 렌더링

삼항 연산자

선택한 카테고리의 값이 all이면 모든 상품을, 특정 카테고리이면 그 카테고리의 상품만 필터링하여 배열에 할당 합니다.

javascript
const filteredProducts = selectedCategory === 'all' 
  ? products  // 'all'이면 전체 상품
  : products.filter(p => p.category === selectedCategory);  // 아니면 필터링

논리 연산자

조건문을 작성하지 않고 논리 연산자를 이용하여 상품 개수가 있을 때, 상품 제목이 없을 때의 내용을 출력합니다.

javascript
// && 연산자로 조건부 렌더링
{products.length > 0 && (
  <div>상품이 있습니다</div>
)}

// || 연산자로 기본값 설정
const title = product.title || '제목 없음';

6. 다중 필터 구현하기

카테고리와 가격대를 동시에 필터링하는 예제입니다.

 
 
javascript
function MultiFilter() {
  const [filters, setFilters] = useState({
    category: 'all',
    minPrice: 0,
    maxPrice: 1000
  });

  const filteredProducts = products.filter(product => {
    // 카테고리 필터
    const categoryMatch = filters.category === 'all' 
      || product.category === filters.category;
    
    // 가격 필터
    const priceMatch = product.price >= filters.minPrice 
      && product.price <= filters.maxPrice;
    
    // 모든 조건을 만족해야 함
    return categoryMatch && priceMatch;
  });

  return (
    <div>
      {/* 카테고리 선택 */}
      <select 
        value={filters.category}
        onChange={(e) => setFilters({...filters, category: e.target.value})}
      >
        <option value="all">전체</option>
        <option value="electronics">전자제품</option>
        <option value="clothing">의류</option>
      </select>

      {/* 가격 범위 */}
      <input 
        type="range" 
        min="0" 
        max="1000"
        value={filters.maxPrice}
        onChange={(e) => setFilters({...filters, maxPrice: e.target.value})}
      />

      {/* 결과 */}
      <div>{filteredProducts.length}개의 상품</div>
    </div>
  );
}

7. 배열 메서드 체이닝

여러 배열 메서드를 연결해서 사용할 수 있습니다.

 
 
javascript
const result = products
  .filter(p => p.category === 'electronics')  // 전자제품만
  .filter(p => p.price < 500)  // 500달러 이하
  .map(p => p.name)  // 이름만 추출
  .sort();  // 알파벳 순 정렬

console.log(result);

8. 성능 최적화: useMemo

필터링 계산이 복잡할 때는 useMemo로 최적화할 수 있습니다.

이렇게 useMemo를 활용하면 복잡한 계산이 필요한 작업을 해당 값이 변경되었을 때만 진행하여 최적화 합니다. 

 
javascript
import { useMemo } from 'react';

function OptimizedFilter() {
  const [products, setProducts] = useState([]);
  const [category, setCategory] = useState('all');

  // 카테고리가 변경될 때만 다시 계산
  const filteredProducts = useMemo(() => {
    console.log('필터링 계산 실행');
    return category === 'all'
      ? products
      : products.filter(p => p.category === category);
  }, [products, category]);

  return <ProductList products={filteredProducts} />;
}

9. 실전 팁

빈 배열 체크

 
javascript
if (filteredProducts.length === 0) {
  return <div>검색 결과가 없습니다.</div>;
}

대소문자 무시 검색

javascript
const filtered = products.filter(p => 
  p.category.toLowerCase() === selectedCategory.toLowerCase()
);

여러 속성으로 검색

javascript
const searchResults = products.filter(product => {
  const searchTerm = searchInput.toLowerCase();
  return (
    product.name.toLowerCase().includes(searchTerm) ||
    product.category.toLowerCase().includes(searchTerm)
  );
});

마무리

카테고리 필터링을 구현하는 데 필요한 핵심 JavaScript 문법을 정리하면:

  • filter(): 조건에 맞는 요소만 걸러내기
  • map(): 배열 변환 및 데이터 추출
  • Set: 중복 제거
  • 스프레드 연산자: 배열/객체 펼치기
  • 삼항 연산자: 간결한 조건 처리
  • useMemo: 성능 최적화

이 문법들을 조합하면 강력하고 유연한 필터링 기능을 구현할 수 있습니다!


더 자세한 내용이 궁금하시다면?
필터링 기능부터 고급 검색까지, 실무에서 바로 쓸 수 있는 쇼핑몰 기능 구현 방법이 궁금하시다면 인프런 강의를 확인해주세요!

반응형

+ Recent posts