ReactJS 로 간단한 프로젝트 만들기

짜스의 하루 ·2024년 5월 28일

어느정도 강의를 들은 뒤, 바로 클론 코딩을 넘어가기 전에
간단한 것들을 만들어 보면서, 복습하고 공부하는 것이 좋을 것 같다는 판단하에,

노마더 코더의 <ReactJS로 영화 웹 서비스 만들기> 강의를 들으면서,
미니 프로젝트를 같이 따라해보고 공부해보기로 했다!

첫 번째, 미니 프로젝트 : ToDOList

ToDoList는 만들어 본 기억이 있어서 쉽게 해나갈 수 있었다.

import React, { useState } from 'react';
import './App.css';

const App = () => {
  const [toDo, setToDo] = useState('');
  const [list, setList] = useState([]);

  const onChangeInput = (e) => {
    setToDo(e.target.value);
  };

  const onClickAdd = () => {
    if (toDo.trim() !== '') {
      // 입력된 값이 공백이 아닌지 확인합니다.
      setList([...list, toDo.trim()]); // list에 새로운 할 일을 추가합니다.
      setToDo(''); // 입력 필드를 초기화합니다.
    }
  };

  const onSubmitForm = (e) => {
    e.preventDefault();
    onClickAdd(); // 폼이 제출될 때 할 일을 추가합니다.
  };

  return (
    <form onSubmit={onSubmitForm}>
      <div>
        <h1>ToDoList : {list.length}</h1>
        <input
          onChange={onChangeInput}
          value={toDo}
          type="text"
          placeholder="write your to do"
        />
      </div>
      <button type="submit">Add To Do</button>
      <hr />
      <ul>
        {list.map(
          (
            item,
            index // 각 항목을 렌더링합니다.
          ) => (
            <li key={index}>{item.toUpperCase()}</li>
          )
        )}
      </ul>
    </form>
  );
};

export default App;

useState 훅을 사용하여 두 가지 상태를 선언한다:

  • toDo: 입력 필드에 입력된 값을 저장한다.
  • list: 할 일 목록을 저장한다.

onChangeInput 함수는 입력 필드의 값이 변경될 때 호출되며, 해당 값을 toDo 상태에 업데이트한다.

onClickAdd 함수는 추가 버튼이 클릭될 때 호출되며, 다음 작업을 수행한다:

  • toDo 값이 공백이 아닌지 확인한다.
  • 공백이 아닌 경우, 입력된 값이 list에 추가된다. 이때 trim() 함수를 사용하여 앞뒤 공백을 제거한다. (lis에 추가할 때, setList([...list, toDo.trim()])와 같이 스프레드 문법을 사용해서 추가해야 한다)
  • 입력 필드는 비워진다.

onSubmitForm 함수는 폼이 제출될 때 호출되며, onClickAdd 함수를 호출한다.
이렇게 하면 Enter 키를 누르거나 폼 제출 버튼을 클릭할 때도 할 일이 추가된다.

간단하게 정말 기능만 구현해 보았다!

두 번째, 미니 프로젝트 : 코인 불러오기!

React를 사용하여 암호화폐 정보를 가져와 표시하는 간단한 애플리케이션이다.

import React, { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const [loading, setLoading] = useState(true);
  const [coins, setCoins] = useState([]);

  useEffect(() => {
    fetch('https://api.coinpaprika.com/v1/tickers')
      .then((response) => response.json())
      .then((json) => {
        setCoins(json);
        setLoading(false); // API 호출이 완료되면 loading 상태를 false로 변경합니다.
      });
  }, []);

  return (
    <>
      <div>
        <h1>This Coins! 💸 {loading ? '' : coins.length}</h1>
        {loading ? (
          <span>loading...</span>
        ) : (
          <select>
            {coins.map((coin) => (
              <option key={coin.id}>
                {coin.name}({coin.symbol}) : {coin.quotes.USD.price} USD
              </option>
            ))}
          </select>
        )}
      </div>
    </>
  );
};

export default App;

useState를 사용하여 두 가지 상태를 선언한다:

  • loading: 데이터가 로드 중인지 여부를 나타내는 상태이다.
  • coins: API에서 가져온 암호화폐 정보를 저장하는 배열이다. 초기 값은 빈 배열([])이다.

useEffect 훅을 사용하여 컴포넌트가 마운트될 때 한 번만 API를 호출한다. fetch 함수를 사용하여 Coinpaprika API의 ticker 엔드포인트에서 데이터를 가져온다

API 호출이 완료되면 .then() 메서드를 사용하여 JSON 데이터를 처리하고, setCoins(json)을 호출하여 암호화폐 정보를 coins 상태에 저장한다. 그리고 setLoading(false)를 호출하여 로딩 상태를 false로 변경한다.

JSX를 사용하여 애플리케이션의 UI를 렌더링한다:
로딩 중일 때는 "loading..." 텍스트가 표시된다.
로딩이 완료되면 coins 배열을 순회하며 각 암호화폐의 정보를 select 요소의 옵션으로 표시한다. 각 옵션에는 암호화폐의 이름, 심볼 및 USD 가격이 표시된다.

그렇다면, 내가 가지고 있는 금액으로 선택한 코인을 얼만큼 구매할 수 있는지 계산해주는 코드를 작성해볼까??

코인을 선택하고, 내 금액으로 코인을 얼만큼 구매할 수 있는지 코드를 작성해 보았다.

import React, { useEffect, useState } from 'react';
import './App.css';
const App = () => {
  const [loading, setLoading] = useState(true);
  const [coins, setCoins] = useState([]);
  const [selectedCoin, setSelectedCoin] = useState(null); // 선택한 코인의 ID를 상태로 관리합니다.
  const [myCoins, setMyCoins] = useState('');
  const [buyCoinAmount, setBuyCoinAmount] = useState(0); // 구매할 코인의 양을 상태로 관리합니다.

  useEffect(() => {
    fetch('https://api.coinpaprika.com/v1/tickers')
      .then((response) => response.json())
      .then((json) => {
        setCoins(json);
        setLoading(false);
      });
  }, []);

  const handleChangeSelectedCoin = (e) => {
    setSelectedCoin(e.target.value); // 선택한 코인의 ID를 업데이트합니다.
  };

  const handleInputChange = (e) => {
    setMyCoins(e.target.value); // 입력된 USD 금액을 상태로 업데이트합니다.
  };

  const buyCoins = () => {
    if (!selectedCoin || !myCoins) return; // 코인과 금액이 선택되지 않았으면 함수를 종료합니다.

    const selectedCoinPrice = coins.find((coin) => coin.id === selectedCoin)
      ?.quotes.USD.price;
    if (!selectedCoinPrice) return; // 선택한 코인의 가격을 찾을 수 없으면 함수를 종료합니다.

    const amount = parseFloat(myCoins) / selectedCoinPrice; // 구매할 코인의 양을 계산합니다.
    setBuyCoinAmount(amount.toFixed(2)); // 소수점 두 자리까지만 표시합니다.
  };

  return (
    <>
      <div>
        <h1>This Coins! 💸 {loading ? '' : coins.length}</h1>
        {loading ? (
          <span>loading...</span>
        ) : (
          <select onChange={handleChangeSelectedCoin}>
            <option value="">Select a coin</option>
            {coins.map((coin) => (
              <option key={coin.id} value={coin.id}>
                {coin.name}({coin.symbol}) : {coin.quotes.USD.price} USD
              </option>
            ))}
          </select>
        )}
        <div>
          My Coins :
          <input type="number" value={myCoins} onChange={handleInputChange} />
          <button onClick={buyCoins}>Calculate</button>
          <div>구매 가능한 코인 수량: {buyCoinAmount}</div>
        </div>
      </div>
    </>
  );
};

export default App;

useState 훅을 사용하여 다양한 상태값을 관리한다. :

  • loading: 데이터가 로딩 중인지 여부를 나타내는 상태값이다.
  • coins: 코인 정보를 담는 배열
  • selectedCoin: 사용자가 선택한 코인의 ID를 저장하는 상태값
  • myCoins: 사용자가 입력한 금액을 나타내는 상태값
  • buyCoinAmount: 구매할 수 있는 코인의 양을 저장하는 상태값

handleChangeSelectedCoin 함수는 사용자가 코인을 선택할 때 호출된다. 선택한 코인의 ID를 selectedCoin 상태값에 업데이트한다.

handleInputChange 함수는 사용자가 입력 필드에 금액을 입력할 때 호출된다. 입력된 금액을 myCoins 상태값에 업데이트한다.

buyCoins 함수는 "Calculate" 버튼을 클릭했을 때 호출더된다. 선택한 코인의 가격을 확인하고, 입력된 금액으로 구매할 수 있는 코인의 양을 계산하여 buyCoinAmount 상태값에 업데이트한다.


세 번째, 미니 프로젝트 : MOVIE APP

fetch 함수로 (https://yts.mx/api/v2/list_movies.json?minimum_rating=9.0&sort_by=year)를 불러온 뒤, 영화에 대한 정보를 화면에 뿌려주는 코드를 작성해 보았다.

여기에서 router를 적용시켜서
메인 화면에서 영화 제목을 누르면 -> <Detail/> 로 이동해 영화에 대한 자세한 정보를 볼 수 있도록 했다.

여기서 중요한 건, 영화마다 가지고 있는 id가 다르기 때문에, id를 받아오고, 그 id를 이동 주소로 등록하는 것 --> id를 가져오고 활용하는 것이 중요했다.


프로젝트 파일 구성은 :

  1. App.js: 라우터를 작성하는 메인 파일
  2. Movie.js: 기본적인 컴포넌트 파일
  3. Detail.jsx: 라우트의 컴포넌트로, 영화의 세부 정보를 보여준다.
  4. Home.jsx: 라우트의 컴포넌트로, 홈 화면을 보여준다.

App.js

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './routes/Home.jsx';
import Detail from './routes/Detail';

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/movie/:id" element={<Detail />} />
      </Routes>
    </Router>
  );
};

export default App;

여기서 중점으로 봐야할 곳은 <Route path="/movie/:id" element={<Detail />} /> 인데,
이 라우트는 /movie/:id 경로에 대해 Detail 컴포넌트를 렌더링한다.
여기서 :id는 URL의 동적 세그먼트로, 해당 영화의 고유 ID 값을 받아와 경로를 지정한다.

Home.jsx

import React, { useState, useEffect } from 'react';
import Movie from '../component/Movie';

const Home = () => {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);

  const getMovies = async () => {
    const response = await fetch(
      `https://yts.mx/api/v2/list_movies.json?minimum_rating=9.0&sort_by=year`
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };

  useEffect(() => {
    getMovies();
  }, []);

  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              id={movie.id} // id를 그대로 전달
              coverImage={movie.medium_cover_image}
              title={movie.title}
            />
          ))}
        </div>
      )}
    </div>
  );
};

export default Home;

useState 훅을 사용하여 loading과 movies 상태 변수를 선언한다. loading의 초기값은

getMovies 함수 정의:

  • fetch를 사용하여 영화 목록을 API로부터 가져오는 비동기 함수를 정의한다.
  • API 호출이 완료되면, json.data.movies 값을 movies 상태 변수에 저장하고 loading 상태를 false로 설정한다.

useEffect 훅 사용:

  • 컴포넌트가 처음 마운트될 때 getMovies 함수를 호출하여 영화 목록을 가져온다.

렌더링 로직:

  • loading 상태가 true이면 "Loading..." 메시지를 표시한다.
  • loading 상태가 false이면, movies 배열을 순회하면서 각 영화를 Movie 컴포넌트로 렌더링한다.
  • 각 Movie 컴포넌트에 영화의 id, coverImage, title 속성을 전달한다.

Movie.jsx

import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

const Movie = ({ coverImage, title, id }) => {
  return (
    <div>
      <img src={coverImage} alt={title} />
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
    </div>
  );
};

Movie.propTypes = {
  coverImage: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired,
};

export default Movie;

Props 받기:

  • Movie 컴포넌트는 coverImage, title, id 세 가지 props를 받는다.
  • 이미지 렌더링: img 태그를 사용하여 coverImage를 이미지로 표시하고, alt 속성에 title을 설정한다.
  • 제목과 링크: h2 태그 내부에 Link 컴포넌트를 사용하여 title을 링크로 감싼다. 이 링크를 클릭하면 /movie/${id} 경로로 이동하여 영화의 상세 페이지를 볼 수 있다.

Detail.jsx

import React, { useEffect, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';

const Detail = () => {
  const { id } = useParams();
  const [movieDetails, setMovieDetails] = useState(null);

  const getMovie = useCallback(async () => {
    const response = await fetch(
      `https://yts.mx/api/v2/movie_details.json?movie_id=${id}`
    );
    const json = await response.json();
    setMovieDetails(json.data.movie);
  }, [id]);

  useEffect(() => {
    getMovie();
  }, [getMovie]);

  return (
    <>
      {movieDetails ? (
        <div>
          <h2>{movieDetails.title}</h2>
          <p>{movieDetails.summary}</p>
          <ul>
            {movieDetails.genres.map((genre, index) => (
              <li key={index}>{genre}</li>
            ))}
          </ul>
          <p>{movieDetails.url}</p>
          <img src={movieDetails.large_cover_image} alt={movieDetails.title} />
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </>
  );
};

export default Detail;

useParams를 사용하여 URL에서 id 값을 가져온다.
useState를 사용하여 movieDetails 상태를 관리한다.

영화 세부 정보를 가져오는 함수:

  • useCallback을 사용하여 getMovie 함수를 정의하고, 영화 ID가 변경될 때마다 새로 생성되도록 한다.
  • API 요청을 보내고 응답 데이터를 movieDetails 상태에 저장한다.

useEffect 훅:

  • 컴포넌트가 마운트되거나 getMovie 함수가 변경될 때 getMovie 함수를 호출한다.

렌더링:

  • movieDetails가 있을 경우 영화의 제목, 요약, 장르, URL, 이미지 등을 화면에 표시한다.
  • 데이터가 로딩 중일 때는 "Loading..." 메시지를 표시한다.

css 적용은 하지 않았다! React 적용을 우선적으로 하기 위해서이다.


첫 화면은 Loding...을 거쳐서 위와 같은 화면이 나오고,
영화 제목을 클릭하면


이렇게 상세 정보를 확인할 수 있다.

fetch를 활용해 API를 가져와서 화면에 뿌려보는 것을 처음 해봤는데
생각보다 재밌기도 하고, JSON()을 활용해보며 잊혀져 가는 것들을 일깨우는 하루였다!

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글