리알못 React: 4. 필터링 및 정렬(Filtering and Sorting)

Table of Content

이 글은 Code with Mosh의 Mastering React 과정을 공부하며 정리한 글입니다. 리알못이라 이상하거나 틀린 내용이 있을 것입니다…

리알못 React: 3. 페이지 매기기(Pagination) 글에 이어 영화 정보를 장르에 따라 필터링하여 보여주거나 특정 컬럼(Column) 기준으로 정렬하는 방법을 한번 알아봅니다.

최대한 간단하게 필터링과 정렬 코드를 설명해보려 했지만 너무 복잡해졌습니다…

사전 준비

Bootstrap 설치

React 프로젝트 경로에서 npm i bootstrap@4.1.1 명령어로 React 프로젝트에 Bootstrap을 설치한 후 index.js 파일에 import 'bootstrap/dist/css/bootstrap.css';를 추가해줍니다.

lodash 설치

lodash는 자바스크립트 유틸리티 라이브러리입니다. 여기서는 배열을 편집하기 위해 사용합니다. React 프로젝트 경로에서 npm i lodash@4.17.10 명령어로 React 프로젝트에 lodash를 설치한 후 lodash를 사용하려는 컴포넌트에서 import _ from 'lodash'; 를 추가해줍니다.

Font Awesome 설치

Font Awesome은 글꼴 및 아이콘을 모아놓은 툴킷입니다. React 프로젝트 경로에서 npm i font-awesome@4.7.0 명령어로 React 프로젝트에 Font Awesome을 설치한 후 index.js 파일에 import 'font-awesome/css/font-awesome.css';를 추가해줍니다.

사용할 데이터 및 모듈

영화정보 및 장르정보

여기서 사용할 데이터는 영화 정보(ID, 제목, 장르, 개봉일 등)장르 정보(ID, 장르명)입니다. 영화 정보는 테이블에 출력하기 위해 사용하며 장르 정보는 데이터를 필터링하기 위해 사용합니다. id_id 값을 서로 같은 값으로 지정하니 필터링이 제대로 되지 않는 문제가 있어 문자열 해싱(MD5)을 통해 값을 지정했습니다.

/***** ./components/services/movieService.js *****/

const movies = [
  {
    id: "5B91E18D48190A9B45867AA9634B5C66",
    title: "기생충",
    genre: { _id: "8826D329FF117EB2559BE04D3DFE330F", name: "블랙 코미디" },
    release: "2019-05-30",
    liked: true
  },
  {
    id: "6526C7787216CB1AF215AC7D1AAA313F",
    title: "라이온 킹",
    genre: { _id: "DD0F413993B0AFD5C8493BCD27E73C21", name: "애니메이션" },
    release: "2019-07-17"
  },
  {
    id: "777B51C04E767A8F8B1A60816F35B4D8",
    title: "날씨의 아이",
    genre: { _id: "DD0F413993B0AFD5C8493BCD27E73C21", name: "애니메이션" },
    release: "2019-10-31"
  },
  {
    id: "DC23B4878476C4E0745C1CC9399E2C61",
    title: "알라딘",
    genre: { _id: "DD63CD95D6DA4B7D098BD64249B23F43", name: "판타지" },
    release: "2019-05-23"
  },
  {
    id: "C7492C5CFC82BE87E37B59F14016EAAC",
    title: "나랏말싸미",
    genre: { _id: "3DA98E33E11A2F37662578A0E8710935", name: "역사" },
    release: "2019-07-24"
  },
  {
    id: "8185799F94A0F218DAFB3F2E9564E64E",
    title: "주전장",
    genre: { _id: "3DA98E33E11A2F37662578A0E8710935", name: "역사" },
    release: "2019-07-25"
  },
  {
    id: "2345E509E8BA2E3170AD4E48F11A3898",
    title: "어벤져스: 엔드게임",
    genre: { _id: "DD63CD95D6DA4B7D098BD64249B23F43", name: "판타지" },
    release: "2019-04-24"
  },
  {
    id: "DCA73F75B0F7D0EF28619496B07DC5DD",
    title: "봉오동 전투",
    genre: { _id: "3DA98E33E11A2F37662578A0E8710935", name: "역사" },
    release: "2019-08-07"
  },
  {
    id: "0529E582348EF19BA7CEEB942DF39A2E",
    title: "김복동",
    genre: { _id: "3DA98E33E11A2F37662578A0E8710935", name: "역사" },
    release: "2019-08-08"
  },
  {
    id: "DEB622A90089E684FC25D2FA9CDA7CD8",
    title: "코코",
    genre: { _id: "DD0F413993B0AFD5C8493BCD27E73C21", name: "애니메이션" },
    release: "2018-01-11"
  },
  {
    id: "F9B50D384A576F0EC8836C05723A0A6D",
    title: "너의 이름은",
    genre: { _id: "DD0F413993B0AFD5C8493BCD27E73C21", name: "애니메이션" },
    release: "2017-01-04"
  },
]

export function getMovies() {
  return movies;
}
/***** ./components/services/genreService.js *****/

const genres = [
  { _id: "8826D329FF117EB2559BE04D3DFE330F", name: "블랙 코미디" },
  { _id: "DD0F413993B0AFD5C8493BCD27E73C21", name: "애니메이션" },
  { _id: "DD63CD95D6DA4B7D098BD64249B23F43", name: "판타지" },
  { _id: "3DA98E33E11A2F37662578A0E8710935", name: "역사" },
]

export function getGenres() {
  return genres.filter(g => g);
}

각 페이지에 대응하는 데이터를 반환하는 함수

데이터를 페이지 별로 나누려면 각 페이지에 대응하는 데이터를 반환시켜주는 함수가 필요합니다. 아래 paginate 함수는 배열(items)을 입력받아 pageNumber와 pageSize에 맞게 배열을 잘라 반환합니다.

/***** ./components/utils/paginate.js *****/

import _ from 'lodash';

export function paginate(items, pageNumber, pageSize) {
  const startIndex = (pageNumber - 1) * pageSize; // 자를 배열의 시작점

  return _(items)
    .slice(startIndex) // 시작점부터 배열을 자르되
    .take(pageSize) // pageSize만큼의 배열을 취함
    .value(); // lodash wrapper 객체를 regular 배열로 변환
}

컴포넌트 구조

App 컴포넌트: 최상위 컴포넌트

단순히 Movies 컴포넌트를 띄웁니다.

/***** App.js *****/

import React from 'react';
import Movies from './components/Movies';

const App = () => {
  return (
    <main className="container">
      <Movies />
    </main>
  )
};

export default App;

Movies 컴포넌트: Movie 관련 컴포넌트 총괄

Movie 관련 정보, 이벤트 및 관련 컴포넌트를 모두 총괄하는 컴포넌트입니다. 화면 좌측엔 장르 메뉴를, 우측엔 영화 정보와 페이지 번호를 표시합니다. 함수형 컴포넌트로 작성해보려 했으나… Hooks 개념이 잘 안 잡혀있어서 그런지 무한루프에 빠지거나 props에 값이 들어가질 않거나 등등의 문제가 생겨 포기… 8ㅅ8

/***** ./components/Movies.js *****/

import React, { Component } from "react";
import { getMovies } from "./services/movieService";
import { getGenres } from "./services/genreService";
import { paginate } from "./utils/paginate";
import ListGroup from "./common/ListGroup";
import MoviesTable from "./MoviesTable";
import Pagination from "./common/Pagination";
import _ from "lodash";

export default class Movies extends Component {
  state = {
    movies: [], // movieService.js에서 가져올 영화 데이터
    genres: [], // genreService.js에서 가져올 장르 데이터
    currentPage: 1, // 현재 페이지 위치
    pageSize: 3, // 한 페이지에 보여줄 콘텐츠 개수
    sortColumn: { path: "title", order: "asc" }, // 정렬 기준
  };

  // 컴포넌트 생성 시 영화와 장르 정보를 state에 저장하는 코드 
  componentDidMount() {
    // genreService.js에선 각각의 장르 정보만 반환하므로 'All Genres'를 새롭게 추가
    // All Genres에 _id 값을 주지 않으면 ListGroup 컴포넌트에서 map() 함수 사용 시 key 속성을 지정할 수 없게 되어 경고 발생
    const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
    this.setState({ movies: getMovies(), genres });
  }

  // 삭제 이벤트 처리 함수
  handleDelete = (movie) => {
    const movies = this.state.movies.filter((m) => m.id !== movie.id);
    this.setState({ movies }); // this.setState({ movies: movies }); 코드와 동일
  };

  // 좋아요 이벤트 처리 함수
  handleLike = (movie) => {
    const movies = [...this.state.movies];
    const index = movies.indexOf(movie);
    movies[index].liked = !movies[index].liked; // 좋아요 <-> 싫어요 반전
    this.setState({ movies });
  };

  // 페이지 변경 이벤트 처리 함수
  handlePageChange = (page) => {
    this.setState({ currentPage: page });
  };

  // 장르 변경 이벤트 처리 함수
  handleGenreSelect = (genre) => {
    this.setState({ selectedGenre: genre, currentPage: 1 }); // 장르가 바뀔 때마다 1페이지로 초기화해야 2페이지 이상 내용이 없는 장르가 제대로 표시됨
  };

  // 컬럼명 클릭 시 정렬 이벤트 처리 함수
  handleSort = (sortColumn) => {
    this.setState({ sortColumn });
  };

  // 선택된 장르와 그 장르를 기준으로 정렬된 데이터를 반환하는 함수
  getPagedData = () => {
    const {
      pageSize,
      currentPage,
      sortColumn,
      selectedGenre,
      movies: allMovies,
    } = this.state;

    // 선택된 장르와 일치하는 영화 데이터를 얻어옴
    // 1. selectedGenre와 그 _id 값이 모두 존재하는 장르는 All Genres를 제외한 장르이므로 ? 조건으로 진입
    // 2. All Genres는 _id값이 없으므로(값: "") : 조건으로 진입
    const filtered =
      selectedGenre && selectedGenre._id
        ? allMovies.filter((m) => m.genre._id === selectedGenre._id)
        : allMovies;

    // orderBy() 함수: 정렬된 배열 반환
    const sorted = _.orderBy(filtered, [sortColumn.path], [sortColumn.order]);

    // allMovies 대신 filtered 전달
    const movies = paginate(sorted, currentPage, pageSize);

    return { totalCount: filtered.length, data: movies };
  };

  render() {
    const { totalCount, data } = this.getPagedData();

    return (
      /* className이 row(행) 및 col(열)인 div 배치: Bootstrap Grid System */
      <div className="row">
        <div className="col-3">
          <ListGroup
            items={this.state.genres}
            selectedItem={this.state.selectedGenre}
            onItemSelect={this.handleGenreSelect}
          />
        </div>

        <div className="col">
          <p>Showing {totalCount} movies in the database.</p>
          <MoviesTable
            movies={data}
            sortColumn={this.state.sortColumn}
            onLike={this.handleLike}
            onDelete={this.handleDelete}
            onSort={this.handleSort}
          />
          <Pagination
            itemsCount={totalCount}
            pageSize={this.state.pageSize}
            currentPage={this.state.currentPage} // 현재 페이지
            onPageChange={this.handlePageChange}
          />
        </div>
      </div>
    );
  }
}

State

영화 데이터를 담을 movies, 영화 데이터를 장르에 따라 필터링하기 위한 genres, 페이지 이동 및 페이지 당 보여줄 영화 정보 개수를 설정할 currentPage 및 pageSize, 어떤 컬럼 기준으로 정렬할지를 명시할 sortColumn을 선언합니다.

componentDidMount()

componentDidMount() 함수는 컴포넌트가 DOM에 완전히 구현되었을 때 호출되는 컴포넌트 생명주기 함수입니다. Movies 컴포넌트가 구현되었을 때 장르 정보를 State에 저장합니다.

이벤트 처리 함수

영화 정보 삭제 또는 페이지 이동 등의 이벤트가 발생한 경우 이를 처리할 함수가 필요합니다.

  • handleDelete() 함수는 전달받은 movie 데이터를 State에서 삭제합니다. Delete 버튼을 클릭했을 때 작동합니다. 삭제할 영화정보를 전달받아 이 영화정보와 id 값이 다른 영화정보들만 filter() 함수를 사용하여 남기면 됩니다.
  • handleLike() 함수는 하트 아이콘을 클릭했을 때 movie의 liked 정보를 반전(true <-> false)시킵니다.
  • handlePageChange() 함수는 페이지 이동 시 이동한 페이지 정보를 state에 설정합니다. 화면 하단의 페이지 번호를 클릭했을 때 작동합니다.
  • handleGenreSelect() 함수는 선택한 장르 정보를 state에 저장합니다. 화면 왼쪽의 장르 메뉴룰 클릭했을 때 작동합니다.
  • handleSort() 함수는 정렬할 컬럼 정보를 state에 저장합니다. 컬럼명을 클릭했을 때 작동합니다.

getPageData()

getPageData() 함수는 정의된 State에 따라 필터링된 영화 정보를 전달하기 위해 만든 함수입니다.

이 함수에선 Movies 정보를 정렬하기 위해 lodash의 orderBy() 함수를 사용합니다. 이 함수에 대한 설명은 https://lodash.com/docs/4.17.15#orderBy 을 참고하시기 바랍니다.

// 첫 번째 매개변수: 정렬할 배열
// 두 번째 매개변수: 정렬할 컬럼 명. 배열로 지정(여러 조건으로 정렬할 수 있기 때문)
// 세 번째 매개변수: 정렬 기준. 배열로 지정(여러 조건으로 정렬할 수 있기 때문)
const sorted = _.orderBy(filtered, [sortColumn.path], [sortColumn.order]);

그리고 이 함수에선 paginate() 함수를 호출하여 현재 페이지에 대응하는 영화 데이터를 전달받습니다. 위에서 설명한 각 페이지에 대응하는 데이터를 반환하는 함수를 참고하시기 바랍니다.

render() 함수

render() 함수는 영화 정보를 화면에 출력합니다. 화면 왼쪽엔 장르 메뉴를, 화면 오른쪽엔 영화 정보 테이블과 이동할 페이지를 표시합니다.

ListGroup 컴포넌트: 장르 리스트 출력

화면 왼쪽에 장르 리스트를 출력하는 컴포넌트입니다

/***** ./components/common/listGroup.js *****/

import React from "react";

const ListGroup = (props) => {
  const {
    items, // 장르 데이터
    textProperty, // 장르 name
    valueProperty, // 장르 _id (Default Props)
    selectedItem, // 선택한 장르 (Default Props)
    onItemSelect, // 장르 선택 이벤트 처리기
  } = props;

  return (
    <ul className="list-group">
      {/* 장르 리스트 생성 */}
      {items.map((item) => (
        <li
          key={item[valueProperty]} // item[valueProperty]는 item._id와 동일
          onClick={() => onItemSelect(item)}
          className={
            item === selectedItem ? "list-group-item active" : "list-group-item"
          }
        >
          {item[textProperty]} {/* item[textProperty]는 item.name과 동일 */}
        </li>
      ))}
    </ul>
  );
};

// Default Props: Props 값이 지정되지 않았을 때 설정할 기본값
ListGroup.defaultProps = {
  textProperty: "name",
  valueProperty: "_id",
};

export default ListGroup;

Props

Movies 컴포넌트에서 전달한 장르 정보 및 이벤트 처리 함수를 Props로 전달받습니다.

Default Props

Default Props는 Props 값이 지정되지 않았을 때 기본값을 설정하기 위해 사용합니다. 여기선 한번 예제로 넣어봤습니다.

return() 함수

Bootstrap List Group을 사용하여 리스트를 생성합니다. 자세한 설명은 https://getbootstrap.com/docs/4.0/components/list-group 링크를 참고하기 바랍니다.

MoviesTable 컴포넌트

영화 정보를 출력하기 위한 테이블과 컬럼 정보 및 관련 컴포넌트를 모두 총괄하는 컴포넌트입니다.

/***** ./components/moviesTable.js *****/

import React from "react";
import Table from "./common/Table";
import Like from "./common/Like";

const MoviesTable = (props) => {
  const columns = [
    // 일반 컬럼
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "release", label: "Release" },
    // Like 및 Delete 컬럼은 Like 컴포넌트와 Button 태그를 보여주기 위해 content라는 속성에 JavaScript ES6 코드를 정의
    {
      key: "like",
      content: (movie) => (
        <Like liked={movie.liked} onClick={() => props.onLike(movie)} />
      ),
    },
    {
      key: "delete",
      content: (movie) => (
        <button
          onClick={() => props.onDelete(movie)}
          className="btn btn-danger btn-sm"
        >
          Delete
        </button>
      ),
    },
  ];

  const { movies, onSort, sortColumn } = props;

  return (
    <Table
      columns={columns}
      data={movies}
      sortColumn={sortColumn}
      onSort={onSort}
    />
  );
};

export default MoviesTable;

columns 배열

테이블에 표시할 컬럼 정보를 명시합니다. path 값은 Movies 배열에서 관련 값을 추출하기 위해 사용할 값입니다.

render() 함수

Table 컴포넌트를 호출합니다. Table 컴포넌트는 테이블 헤더를 보여주는 TableHeader 컴포넌트와 테이블 내용을 보여주는 TableBody 컴포넌트로 구성됩니다. 이렇게 구현한 이유는 코드의 재사용성을 높이기 위함입니다.

Table 컴포넌트: 테이블 해더 및 내용 배치

테이블 헤더와 내용을 각각 보여주는 컴포넌트입니다. 테이블 헤더를 보여주는 TableHeader 컴포넌트와 테이블 내용을 보여주는 TableBody 컴포넌트로 구성되는데, 그 이유는 코드의 재사용성을 높이기 위함입니다.

/***** ./components/common/table.js *****/

import React from "react";
import TableHeader from "./TableHeader";
import TableBody from "./TableBody";

// 코드 재사용성을 높이기 위해 Table = TableHeader + TableBody 형태로 구성
const Table = ({ columns, sortColumn, onSort, data }) => {
  return (
    <table className="table">
      <TableHeader // 테이블 헤더 컴포넌트
        columns={columns}
        sortColumn={sortColumn}
        onSort={onSort}
      />
      <TableBody // 테이블 내용 컴포넌트
        data={data}
        columns={columns}
      />
    </table>
  );
};

export default Table;

TableHeader 컴포넌트: 테이블 헤더 구현

테이블 헤더(컬럼)를 구현한 컴포넌트입니다. 컬럼을 보여줄뿐만 아니라 컬럼을 클릭하면 어떤 컬럼을 기준으로 정렬해야 할지 부모(Movies) 컴포넌트에게 알려줍니다. 참고로 부모 컴포넌트에서 직접 정렬을 시도할 수 있지만 역정렬이 되지 않습니다…

/***** ./components/common/tableHeader.js *****/

import React from "react";

const TableHeader = (props) => {
  // 부모(Movies) 컴포넌트에 정렬 이벤트 발생을 알리는 코드
  // 참고로, 부모 컴포넌트에서 정렬을 시도하면 되긴 하지만 역정렬은 불가
  const raiseSort = (path) => {
    const sortColumn = { ...props.sortColumn };

    if (sortColumn.path === path)
      // 정렬할 컬럼명이 기존과 같다면 역정렬(즉, 같은 컬럼을 두 번 이상 클릭했을 때)
      sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
    else {
      // 정렬할 컬럼명이 기존과 다르면 역정렬하지 않음(즉, 새로운 컬럼을 클릭했을 때)
      sortColumn.path = path;
      sortColumn.order = "asc";
    }

    props.onSort(sortColumn);
  };

  // Font Awesome을 사용하여 컬럼 클릭 시 오름차순 또는 내림차순 아이콘을 표시하는 태그를 반환하는 코드
  const renderSortIcon = (column) => {
    const { sortColumn } = props;

    if (column.path !== sortColumn.path) return null; // 현재 컬럼 기준으로 정렬된 게 아니라면 아이콘을 반환하지 않음
    if (sortColumn.order === "asc") return <i className="fa fa-sort-asc" />;

    return <i className="fa fa-sort-desc" />;
  };

  return (
    // 테이블 컬럼 출력
    <thead>
      <tr>
        {props.columns.map((column) => (
          <th
            key={column.path || column.key} // column.path 값을 지정하되 그렇지 않은 경우(like 및 delete 컬럼명) column.key 값을 지정
            onClick={() => raiseSort(column.path)}
          >
            {column.label} {renderSortIcon(column)}{" "}
            {/* 컬럼명 오른쪽에 정렬 아이콘 표시 */}
          </th>
        ))}
      </tr>
    </thead>
  );
};

export default TableHeader;

raiseSort() 함수

정렬 이벤트를 상위 컴포넌트(MoviesTable 및 Movies 컴포넌트)에 전달합니다.

renderSortIcon() 함수

컬럼 클릭 시 Font Awesome을 사용하여 컬럼명 오른쪽에 오름차순 및 내림차순 아이콘을 표시하는 태그를 반환합니다. 이 아이콘에 대해선 https://fontawesome.com/v4.7.0/icon/sort-asc 을 참고해주시기 바랍니다.

render() 함수

전달받은 컬럼 정보를 토대로 테이블 컬렁을 출력합니다.

TableBody 컴포넌트: 테이블 내용 구현

영화 정보를 테이블 형태로 출력하는 컴포넌트입니다.

/***** ./components/common/TableBody.js ****/

import React from "react";
import _ from "lodash";

const TableBody = (props) => {
  // item(movie)에서 column에 대응하는 값을 가져오는 함수
  const renderCell = (item, column) => {
    // column에 content 값이 있다면 content 값에 정의된 화살표 코드에 item(movie) 객체를 대입
    if (column.content) {
      return column.content(item);
    }

    // column에 content 값이 없다면 item(movie) 객체에서 column에 대응하는 값을 꺼내옴
    return _.get(item, column.path); // lodash의 get(obejct, path) 함수: object의 path에서 값을 꺼내옴
  };

  // 유일한 키 값을 반환하는 함수
  const createKey = (item, column) => {
    return item._id + (column.path || column.key); // column 배열엔 path 및 key 속성이 모두 정의된 객체가 없으므로 둘 중 하나로부터 값을 가져옴
  };

  const { data, columns } = props;

  return (
    <tbody>
      {/* 행: 모든 영화 정보(data) 중 하나의 영화 정보(item)씩 꺼내며 그려 나감 */}
      {data.map((item) => ( 
        <tr key={item._id}>
          {/* 열: 하나의 영화 정보(item)에서 각 컬럼명(column)에 대응하는 값을 꺼내옴 */}
          {columns.map((column) => (
            <td key={createKey(item, column)}>
              {renderCell(item, column)}
            </td>
          ))}
        </tr>
      ))}
    </tbody>
  );
};

export default TableBody;

renderCell() 함수

item(movie) 정보 중에 column에 대응하는 정보를 꺼내옵니다.

columns 배열은 다음과 같습니다.

columns = [
  // 일반 컬럼: 
  { path: 'title', label: 'Title' },
  { path: 'genre.name', label: 'Genre' },
  { path: 'release', label: 'Release' },

  // Like 및 Delete는 Like 컴포넌트와 Button 태그를 보여주기 위해 content라는 속성에 JavaScript ES6 코드를 정의
  { 
    key: 'like',
    content: movie => <Like liked={movie.liked} onClick={() => this.props.onLike(movie)} />
  },
  {
    key: 'delete',
    content: movie => <button onClick={() => this.props.onDelete(movie)} className="btn btn-danger btn-sm">Delete</button>
  },
]

label 명이 Title, Genre, Release인 요소는 content라는 값이 없습니다. 반면 key 값이 like, delete인 요소는 content 값이 있습니다. content 값을 별도로 구현한 이유는 Title, Genre, Release 값은 movie 객체에서 컬럼명에 대응하는 값을 꺼내 보여주면 되지만 like와 delete는 값을 꺼내서 보여주는 게 아니라 별도로 새로운 컴포넌트와 태그를 구현해서 보여주는 코드를 반환하기 위함입니다. 즉 Title, Genre, Release와 Like, Delete의 행동을 다르게 하기 위함입니다.

createKey() 함수

map() 함수룰 사용하여 만들어낼 태그에는 key 속성을 지정해야 합니다. 이 때 key 값은 유일해야 합니다. 이 때 사용하기 위해 만든 함수입니다.

render() 함수

전달받은 data(movies) 정보를 토대로 테이블 내용을 출력합니다.

Like 컴포넌트

Font Awesome을 사용하여 하트 모양의 아이콘을 반환합니다. 영화 정보에는 like 속성이 있는 객체와 없는 객체가 있습니다. 이 중 like 속성이 true인 경우 속이 꽉 찬 하트를, false이거나 like 속성이 아예 없는 경우 속이 빈 하트를 반환합니다.

이 컴포넌트는 columns 배열에서 사용됩니다.

/***** ./components/common/Like.js *****/

import React from "react";

const Like = (props) => {
  let classes = "fa fa-heart"; // 속이 꽉 찬 하트
  if (!props.liked) classes += "-o"; // 속이 빈 하트

  return (
    <i
      onClick={props.onClick}
      style={{ cursor: "pointer" }}
      className={classes}
      aria-hidden="true"
    ></i>
  );
};

export default Like;

Pagination 컴포넌트

영화 정보의 수에 비례하여 화면 하단에 페이지 수를 출력하는 컴포넌트입니다.

/***** ./components/common/pagination.js *****/

import React from "react";
import PropTypes from "prop-types";
import _ from "lodash";

const Pagination = (props) => {
  const { itemsCount, pageSize, currentPage, onPageChange } = props;
  const pageCount = Math.ceil(itemsCount / pageSize);

  if (pageCount === 1) return null; // 1페이지 뿐이라면 페이지 수를 보여줄 필요가 없음

  const pages = _.range(1, pageCount + 1); // lodash의 range() 함수: 1부터 pageCount+1 까지 1씩 증가하는 배열 생성

  return (
    <nav>
      <ul className="pagination">
        {pages.map((page) => (
          <li
            key={page}
            className={page === currentPage ? "page-item active" : "page-item"}
          >
            {" "}
            {/* Bootstrap을 사용하여 현재 페이지를 시각적으로 표시 */}
            <a className="page-link" onClick={() => onPageChange(page)}>
              {page}
            </a>{" "}
            {/* 페이지 번호 클릭 이벤트 처리 함수 지정 */}
          </li>
        ))}
      </ul>
    </nav>
  );
};

// PropTypes를 사용한 타입 체크
Pagination.propTypes = {
  itemsCount: PropTypes.number.isRequired,
  pageSize: PropTypes.number.isRequired,
  currentPage: PropTypes.number.isRequired,
  onPageChange: PropTypes.func.isRequired,
};

export default Pagination;

페이지 수를 계산하는 코드

movie 정보 개수를 페이지에 보여줄 정보 개수로 나누어 올림하면 몇 페이지가 필요한지 알 수 있습니다.

return 값

Bootstrap을 사용하여 총 페이지를 표시하는 태그를 구현한 후 반환합니다.

PropTypes를 사용한 타입 체크

이건 참고로 넣은 코드입니다. PropTypes를 사용하여 Props에 어떤 값이 들어가야 하는지 콘솔을 통해 체크할 수 있습니다.

댓글 남기기