어느정도 강의를 들은 뒤, 바로 클론 코딩을 넘어가기 전에
간단한 것들을 만들어 보면서, 복습하고 공부하는 것이 좋을 것 같다는 판단하에,
노마더 코더의 <ReactJS로 영화 웹 서비스 만들기> 강의를 들으면서,
미니 프로젝트를 같이 따라해보고 공부해보기로 했다!
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 훅을 사용하여 두 가지 상태를 선언한다:
onChangeInput 함수는 입력 필드의 값이 변경될 때 호출되며, 해당 값을 toDo 상태에 업데이트한다.
onClickAdd 함수는 추가 버튼이 클릭될 때 호출되며, 다음 작업을 수행한다:
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를 사용하여 두 가지 상태를 선언한다:
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 훅을 사용하여 다양한 상태값을 관리한다. :
handleChangeSelectedCoin 함수는 사용자가 코인을 선택할 때 호출된다. 선택한 코인의 ID를 selectedCoin 상태값에 업데이트한다.
handleInputChange 함수는 사용자가 입력 필드에 금액을 입력할 때 호출된다. 입력된 금액을 myCoins 상태값에 업데이트한다.
buyCoins 함수는 "Calculate" 버튼을 클릭했을 때 호출더된다. 선택한 코인의 가격을 확인하고, 입력된 금액으로 구매할 수 있는 코인의 양을 계산하여 buyCoinAmount 상태값에 업데이트한다.
fetch 함수로 (https://yts.mx/api/v2/list_movies.json?minimum_rating=9.0&sort_by=year)를 불러온 뒤, 영화에 대한 정보를 화면에 뿌려주는 코드를 작성해 보았다.
여기에서 router를 적용시켜서
메인 화면에서 영화 제목을 누르면 -> <Detail/> 로 이동해 영화에 대한 자세한 정보를 볼 수 있도록 했다.
여기서 중요한 건, 영화마다 가지고 있는 id가 다르기 때문에, id를 받아오고, 그 id를 이동 주소로 등록하는 것 --> id를 가져오고 활용하는 것이 중요했다.

프로젝트 파일 구성은 :
App.js: 라우터를 작성하는 메인 파일Movie.js: 기본적인 컴포넌트 파일Detail.jsx: 라우트의 컴포넌트로, 영화의 세부 정보를 보여준다.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 함수 정의:
useEffect 훅 사용:
렌더링 로직:
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 받기:
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 상태를 관리한다.
영화 세부 정보를 가져오는 함수:
useEffect 훅:
렌더링:
css 적용은 하지 않았다! React 적용을 우선적으로 하기 위해서이다.

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

이렇게 상세 정보를 확인할 수 있다.
fetch를 활용해 API를 가져와서 화면에 뿌려보는 것을 처음 해봤는데
생각보다 재밌기도 하고, JSON()을 활용해보며 잊혀져 가는 것들을 일깨우는 하루였다!