개발환경설치

- node.js

npx create-react-app <프로젝트명> [--template typescript]
cd <프로젝트명>
npm install axios --save
npm install react-router-dom --save
npm run start // 3000 포트로 실행
npm run build // build 폴더 내 생성된 파일들을 웹서버가 바라보는 폴더에 복사 붙여넣기

 

App.js

import './App.css';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import ItemList from './ItemList';
import ItemDetail from './ItemDetail';
 
function App() {
  return (
    <div align='center'>
      <BrowserRouter>
        <Routes>
          <Route path='/' element={<ItemList/>} />
          {
            /* exact의 경우, 해당 경로와 확실하게 일치하여야 렌더링 수행 */
          }
          <Route path='/detail/:index' element={<ItemDetail/>} />
          {
          /* 
            <Route path="/error" element={<ErrorPage />} />
            <Route path="/*" element={<Navigate to="/error" />} /> 
          */
          }
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

- react-router-dom v6 이전

더보기
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import ItemList from './ItemList';
import ItemDetail from './ItemDetail';
 
function App() {
  return (
    <div align='center'>
      <Router>
        <Switch>
          <Route path='/' component={ItemList} />
          {/* exact의 경우, 해당 경로와 확실하게 일치하여야 렌더링 수행 */}
          <Route path='/detail/:index' component={ItemDetail} exact />
        </Switch>
      </Router>
    </div>
  );
}

export default App;
ItemList.js
import axios from 'axios';
import React, { useState, useEffect, useCallback } from 'react';
import ItemListItem from './ItemListItem';
import { useNavigate } from 'react-router-dom';
 
const ItemList = () => {
  // 상단 상태값
  const [currentTime, setCurrentTime] = useState('');
  // 상단 Select 상태값
  const [chartType, setChartType] = useState('domestic');
  // 메인 리스트 상태값
  const [chartList, setChartList] = useState([]);

  // const [loading, setLoading] = useState(false);
  const navigate = useNavigate();
 
  // API 목록 조회 (쿼리에 따른)
  const loadItemList = (chartTypeStr) => {
    // setLoading(true)
    axios.get('http://localhost:3300/v1/chart/' + chartTypeStr)
      .then((response) => {
        setChartList(response.data.chartList);
        // setLoading(false)
      })
      .catch((error) => {
        console.log(error);
        // setLoading(false)
      });
  };
 
  // 이벤트 처리 함수
  const onSelectItem = (chartTypeStr) => {
    setChartType(chartTypeStr);
    loadItemList(chartTypeStr);
  };

  // 이벤트 처리 함수
  const onClickTitle = (id) => {
    // window.location.assign('/detail/' + id);
    navigate('/detail/' + id)
  };
 
  // renderList 
  const drawList = useCallback(() => {
    const ItemList = chartList.map((item) => {
      return (<ItemListItem key={item.id} item={item} onClickCallback={onClickTitle}></ItemListItem>);
    });
    return ItemList;
  }, [chartList]);
 
  // onCreate() : 초기화 작업
  useEffect(() => {
    let now = new Date();
    let hour = now.getHours() < 10 ? '0' + now.getHours() : now.getHours();
    let min = now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes();
    let curremtTimeStr = now.getFullYear() + '년 ' + (now.getMonth() + 1) + '월 ' + now.getDate() + '일 ' + hour + ':' + min;
    setCurrentTime(curremtTimeStr);
    setChartType('domestic');
    // 초기 데이터 로드
    loadItemList('domestic');
  }, []);
 
  return (
    <>
      {/* {loading && <div> loading... </div>} */}
      <table width='600px' border='0px'>
        <tr><td align='center' colspan='4'><h1>음악 차트</h1></td></tr>
        <tr><td align='center' colspan='4'>{currentTime}</td></tr>
        <tr>
          <td colspan='4'>
            <button style={chartType === 'domestic' ? { fontWeight: 'bold', color: 'red', cursor: 'pointer' } : { cursor: 'pointer' }} onClick={() => onSelectItem('domestic')}> 국내 </button> 
            <button style={chartType === 'overseas' ? { fontWeight: 'bold', color: 'red', cursor: 'pointer' } : { cursor: 'pointer' }} onClick={() => onSelectItem('overseas')}> 해외 </button><br /><br />
          </td>
        </tr>
        {drawList()}
      </table>
    </>
  );
}
 
export default ItemList;

ItemListItem.js

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import {useNavigate} from 'react-router-dom' 


// props 
// - item
const ItemListItem = (props) => {
  const [title, setTitle] = useState(props.item.title);
  const [singer, setSinger] = useState(props.item.singer);
  const [url, setUrl] = useState('/detail/' + props.item.id);
  const navigate = useNavigate();

  const onClickTitle = () => {
    props.onClickCallback(props.item.id);
  }

  // useEffect(() => {
  //   if (props.item.title.length > 18) {
  //     setTitle(props.item.title.substring(0, 18) + '...');
  //   }
  //   if (props.item.singer.length > 15) {
  //     setSinger(props.item.singer.substring(0, 15) + '...');
  //   }
  // }, [title, singer]);
 
  return (
    <tr>
      <td width='20px'>{props.item.rank}</td>
      <td width='50px'>
        <img src={props.item.imageUrl} />
      </td>
      <td width='250px'>
        <Link to={url}>
          <div style={{width: '70px',
                      padding:'0 5px',
                      overflow: 'hidden',
                      textOverflow: 'ellipsis',
                      whiteSpace: 'nowrap'}}>
          {title}
          </div>
        </Link>
      </td>
      <td width='180px' align='right'>{singer}</td>
    </tr>
  );
}
 
export default ItemListItem;

ItemDetail.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
 
const ItemDetail = ({ match }) => {
  // const index = match.params.index 
  const { index } = useParams();
  const [id, setId] = useState(index);
  const [detail, setDetail] = useState(null);

  const navigate = useNavigate();
 
  // onCreate(): 초기화
  useEffect(() => {
    if (id > 0) {
      axios.get('http://localhost:3300/v1/chart/detail/' + id)
        .then((response) => {
          if (response.data.chart.title === undefined) {
            alert('곡 정보가 존재하지 않습니다.');
            // window.location.assign('/');
            navigate('/');
          }
          else {
            setDetail(response.data.chart);
          }
        }).catch((error) => {
          console.log(error);
        });
    }
  }, [id]);
 
  return (
    <div>
      <div align='center'>
        {
          detail &&
          <div>
            <div><h2></h2></div>
            <table border='0'>
              <tr>
                <td colSpan='2'>
                  {/* 뒤로가기 구현 */}
                  <button style={{ cursor: 'pointer' }} onClick={() => 
                    // window.location.assign('/')
                    navigate('/')
                    }>←</button>
                </td>
              </tr>
              <tr>
                <td colSpan='2' align='center'>
                  <h1>{detail.title}</h1>
                  <h3>{detail.singer}</h3>
                  <br></br>
                </td>
              </tr>
              <tr>
                <td width='100px' align='left'><h4>작사</h4></td>
                <td width='300px' align='left'>{detail.lyricist}</td>
              </tr>
              <tr>
                <td width='100px' align='left'><h4>작곡</h4></td>
                <td width='300px' align='left'>{detail.melodizer}</td>
              </tr>
              <tr>
                <td width='100px' align='left'><h4>장르</h4></td>
                <td width='300px' align='left'>{detail.genre}</td>
              </tr>
            </table>
          </div>
        }
      </div>
    </div >
  );
}
 
export default ItemDetail;

 

# 참고

- 간단 스크롤링 구현하기 : [React] Infinite Scroll 구현하기. React로 infinite scroll을 구현해보자 | by Diana Lee | Medium

- 간단 페이징 구현하기 : [React] 리액트 페이지네이션(pagination) 구현하기 (2) | ChanBLOG (chanhuiseok.github.io)

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기