React 쇼핑몰 상품 목록 컴포넌트 분석: 검색·정렬·필터 구현 완전 해부
React

React 쇼핑몰 상품 목록 컴포넌트 분석: 검색·정렬·필터 구현 완전 해부


1편에서 프로젝트 구조를 파악했죠? 이제 상품 목록이 화면에 어떻게 표시되는지 코드를 분석해볼게요.

이번 편에서는 상품 데이터가 화면에 렌더링되는 과정과 검색/정렬 기능이 어떻게 동작하는지 살펴봅니다.

이번 편에서 알아볼 내용

  • ✅ 상품 데이터 파일 구조 (fruit.js, veggie.js)
  • ✅ map으로 상품 목록 렌더링하는 방법
  • ✅ Products 컴포넌트 구조
  • ✅ filter로 구현된 검색 기능
  • ✅ sort로 구현된 정렬 기능
  • ✅ useNavigate로 페이지 이동하는 방법

전체 데이터 흐름

먼저 데이터가 어떻게 흘러가는지 볼게요:

graph TD
    A["fruit.js / veggie.js<br/>(상품 데이터)"] --> B["App.js<br/>(useState로 상태 저장)"]
    B --> C["map으로 반복"]
    C --> D["Products / ComVeggie<br/>(상품 카드 컴포넌트)"]
    D --> E["화면에 표시"]
    
    F["검색어 입력"] --> G["filter로 필터링"]
    G --> C
    
    H["정렬 선택"] --> I["sort로 정렬"]
    I --> C
    
    style A fill:#e1f5ff
    style B fill:#ffe1e1
    style D fill:#e1ffe1
    style F fill:#fff4e1
    style H fill:#e1d5ff

상품 데이터 파일 만들기

fruit.js - 과일 데이터 (전체 코드)

src/db/fruit.js 파일을 만들어주세요:

let data = [
  {
    id: 1,
    title: "수박",
    imgUrl: "img/fruit1.jpg",
    content: "당도선별 프리미엄 고당도 하우스수박 5~6kg",
    price: 29000,
  },
  {
    id: 2,
    title: "참외",
    imgUrl: "img/fruit2.jpg",
    content: "산지직송 성주 달콤참외 3kg",
    price: 16900,
  },
  {
    id: 3,
    title: "사과",
    imgUrl: "img/fruit3.jpg",
    content: "고씨네농장 고당도 청송사과, 햇부사 5kg",
    price: 13000,
  },
  {
    id: 4,
    title: "바나나",
    imgUrl: "img/fruit4.jpg",
    content: "델몬트 필리핀 바나나 6kg",
    price: 15000,
  },
  {
    id: 5,
    title: "딸기",
    imgUrl: "img/fruit5.jpg",
    content: "설향 딸기, 킹스베리 1박스",
    price: 14500,
  },
  {
    id: 6,
    title: "오렌지",
    imgUrl: "img/fruit6.jpg",
    content: "캘리포니아 네이블 오렌지 4kg",
    price: 17000,
  },
  {
    id: 7,
    title: "토마토",
    imgUrl: "img/fruit7.jpg",
    content: "행복한 농부 완숙찰토마토 5kg",
    price: 20000,
  },
  {
    id: 8,
    title: "포도",
    imgUrl: "img/fruit8.jpg",
    content: "팜앤프룻 프리미엄 아삭한 애플청포도",
    price: 25000,
  },
  {
    id: 9,
    title: "망고",
    imgUrl: "img/fruit9.jpg",
    content: "프리미엄 제주애플망고 1박스",
    price: 18500,
  },
];

export default data;

각 상품은 이런 속성을 가지고 있어요:

  • id - 고유 식별자 (과일: 1~9)
  • title - 상품명
  • imgUrl - 이미지 경로
  • content - 상품 설명
  • price - 가격

veggie.js - 채소 데이터 (전체 코드)

src/db/veggie.js 파일을 만들어주세요:

let veggie = [
  {
    id: 10,
    title: "당근",
    imgUrl: "img/veggie/veggie1.jpg",
    content: "국내산 햇당근 1kg",
    price: 11900,
  },
  {
    id: 11,
    title: "옥수수",
    imgUrl: "img/veggie/veggie2.jpg",
    content: "쫀득쫀득 찰옥수수 250g",
    price: 29000,
  },
  {
    id: 12,
    title: "감자",
    imgUrl: "img/veggie/veggie3.jpg",
    content: "강원도 두백산 수미 감자 33kg",
    price: 30000,
  },
];

export default veggie;

[!TIP] 과일은 id가 1~9, 채소는 id가 10 이상이에요. 이걸로 나중에 채소인지 과일인지 구분해요!

Products 컴포넌트 분석

Props로 데이터 받기

src/components/Products.js 코드:

import React from "react";
import { Nav } from "react-bootstrap";
import { useNavigate } from "react-router-dom";

const Products = ({ id, title, price, imgUrl, content }) => {   
    const navigate = useNavigate();
    
    return (
        <div className="col-md-4" style={{ marginBottom: "50px" }}>
            <Nav.Link className="c1" onClick={() => navigate(`/detail/${id}`)}>
                <img src={imgUrl} width="80%" alt={title} />
                <h5 style={{ marginTop: "10px" }}>{title}</h5>
                <p>{content}</p>
                <span>{price}</span>
            </Nav.Link>
        </div>
    );
};

export default Products;

핵심 포인트:

graph LR
    A["App.js에서<br/>데이터 전달"] --> B["Products<br/>id, title, price..."]
    B --> C["화면에<br/>카드 표시"]
    
    D["클릭!"] --> E["useNavigate"]
    E --> F["/detail/1로 이동"]
    
    style A fill:#e1f5ff
    style B fill:#ffe1e1
    style C fill:#e1ffe1
    style E fill:#fff4e1
  1. 구조 분해 할당(Destructuring): { id, title, price, imgUrl, content } - props를 한 번에 꺼내서 사용
  2. useNavigate: 클릭하면 상세 페이지로 이동
  3. 템플릿 리터럴: /detail/\${id} 형태로 동적 URL 생성

App.js에서 상품 목록 렌더링

App.js에서 Products 컴포넌트를 어떻게 사용하는지 볼게요:

1. 데이터 import 및 상태 설정

import data from "./db/fruit";
import data2 from "./db/veggie";
import Products from "./components/Products";

function App() {
  const [fruit, setFruit] = useState(data);
  let [veggie, setVeggie] = useState(data2);
  let [input, setInput] = useState("");  // 검색어
  // ...
}

2. map으로 상품 목록 표시하기

<div className="container" style={{ marginTop: "30px" }}>
  <div className="row">                    
    {fruit.map((fruit) => (
      <Products {...fruit} key={fruit.id} />
    ))}
  </div>
</div>

map이 하는 일:

graph LR
    subgraph "fruit 배열"
        A1["수박"]
        A2["참외"]
        A3["사과"]
    end
    
    M["map 함수"]
    
    subgraph "Products 컴포넌트들"
        B1["🍉 수박 카드"]
        B2["🍈 참외 카드"]
        B3["🍎 사과 카드"]
    end
    
    A1 --> M
    A2 --> M
    A3 --> M
    M --> B1
    M --> B2
    M --> B3
    
    style M fill:#ffe1e1
  • fruit.map() - 배열의 각 아이템에 대해 함수 실행
  • {...fruit} - 스프레드 연산자로 모든 속성 전달 (id={id} title={title} … 와 같음)
  • key={fruit.id} - React가 각 아이템을 구분하는 고유 키

검색 기능 분석

검색 입력창

<input
  placeholder="상품명을 입력하세요"
  onChange={(e) => setInput(e.target.value)}
  value={input}
  style={{
    padding: "10px",
    borderRadius: "4px",
    border: "1px solid #ccc",
    width: "250px"
  }}
/>
  • onChange - 입력할 때마다 실행
  • e.target.value - 입력된 값
  • setInput() - 상태 업데이트

filter로 검색 결과 필터링

{fruit
  .filter((item) => {
    return item.title.toLowerCase().includes(input.toLowerCase());
  })
  .map((fruit) => (
    <Products {...fruit} key={fruit.id} />
  ))}

filter가 하는 일:

graph TD
    A["전체 상품<br/>수박, 참외, 사과..."] --> B{"검색어: '수'"}
    B --> C["filter 실행"]
    C --> D["'수'가 포함된 상품만"]
    D --> E["수박, 옥수수"]
    E --> F["화면에 표시"]
    
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style E fill:#e1ffe1
  • filter() - 조건에 맞는 아이템만 반환
  • toLowerCase() - 대소문자 구분 없이 검색
  • includes() - 문자열 포함 여부 확인

정렬 기능 분석

정렬 함수들

App.js에 정의된 정렬 함수들:

// 이름순 정렬
const sortByName = () => {
  let sortedFruit = [...fruit].sort((a, b) => (a.title > b.title ? 1 : -1));
  setFruit(sortedFruit);
};

// 낮은 가격순
const sortByPriceLowToHigh = () => {
  let sortedFruit = [...fruit].sort((a, b) => a.price - b.price);
  setFruit(sortedFruit);
};

// 높은 가격순
const sortByPriceHighToLow = () => {
  let sortedFruit = [...fruit].sort((a, b) => b.price - a.price);
  setFruit(sortedFruit);
};

sort가 하는 일:

graph LR
    subgraph "원본 배열"
        A["수박 29000원"]
        B["참외 16900원"]
        C["사과 13000원"]
    end
    
    S["sort<br/>(낮은 가격순)"]
    
    subgraph "정렬된 배열"
        D["사과 13000원"]
        E["참외 16900원"]
        F["수박 29000원"]
    end
    
    A --> S
    B --> S
    C --> S
    S --> D
    S --> E
    S --> F
    
    style S fill:#ffe1e1

[!IMPORTANT] [...fruit] - 스프레드 연산자로 새 배열을 만들어요! 원본 배열을 직접 수정하면 React가 변화를 감지 못해요.

select로 정렬 선택하기

<select
  onChange={(e) => {
    if (e.target.value === "low") sortByPriceLowToHigh();
    if (e.target.value === "high") sortByPriceHighToLow();
    if (e.target.value === "name") sortByName();
  }}
>
  <option value="">정렬 선택</option>
  <option value="low">낮은 가격순</option>
  <option value="high">높은 가격순</option>
  <option value="name">이름순</option>
</select>

더 보기 버튼과 Axios

외부 데이터를 추가로 불러오는 “더 보기” 버튼:

<Button
  variant="outline-success"
  onClick={() => {
    if (count === 1) {
      axios
        .get("https://sinaboro.github.io/react_data/veggie2.json")
        .then((result) => {
          let copy = [...veggie, ...result.data];
          setVeggie(copy);
          setCount(count + 1);
        });
    }
    // ...
  }}
>
  + 3개 상품 더 보기
</Button>

데이터 로딩 흐름:

sequenceDiagram
    participant U as 사용자
    participant A as App.js
    participant S as 외부 서버
    
    U->>A: "더 보기" 클릭
    A->>S: axios.get() 요청
    S-->>A: JSON 데이터 응답
    A->>A: [...veggie, ...result.data]
    A->>A: setVeggie() 상태 업데이트
    A-->>U: 화면에 새 상품 표시

정리

오늘 2편에서 분석한 내용:

graph LR
    A["데이터 파일"] --> B["useState"]
    B --> C["filter<br/>(검색)"]
    C --> D["sort<br/>(정렬)"]
    D --> E["map<br/>(렌더링)"]
    E --> F["컴포넌트"]
    
    style C fill:#fff4e1
    style D fill:#e1d5ff
    style E fill:#ffe1e1

핵심 포인트:

  1. 📦 map - 배열을 반복해서 컴포넌트 생성
  2. 🔍 filter - 조건에 맞는 데이터만 선별
  3. 📊 sort - 데이터 정렬 (새 배열로 복사해서!)
  4. 🔗 스프레드 연산자 - {...data}로 props 전달, [...arr]로 배열 복사
  5. 🚀 useNavigate - 프로그래밍으로 페이지 이동

다음 편에서는 상품 상세 페이지와 장바구니가 어떻게 연결되어 있는지 알아볼게요!


시리즈 전체 보기


댓글 남기기

궁금한 점이나 나누고 싶은 이야기가 있다면 자유롭게 남겨주세요.
여러분의 소중한 의견이 블로그를 성장시킵니다! 🌱

💡 오늘의 명언

"꽃은 자신을 누구와 비교하지 않는다. 그저 피어날 뿐이다."

✍️ 작성하기

💬0개의 댓글

댓글을 불러오는 중입니다...