1. API 받기 & axios 인스턴스 생성 및 요청 보내기
MBTI 테스트나 to do 어플리케이션과 달리 이번 클론 코딩은 API를 받아 화면을 구성한다.
The Movie Database (TMDB)
Welcome. Millions of movies, TV shows and people to discover. Explore now.
www.themoviedb.org
이 곳에서 회원가입 후 API Key를 받는다.
API URL은 아래와 같다.
- 최신 영화: https://api.themoviedb.org/3/movie/latest?api_key=<<api_key>>&language=en-US
- 디테일한 정보: https://api.themoviedb.org/3/movie/{movie_id}?api_key={{api_key>>&language=en-US
- 영화 리뷰: https://api.themoviedb.org/3/movie/{movie_id}/reviews?api_key=<<api_key>>&language=en-US&page=1
- 영화 이미지 : https://image.tmdb.org/t/p/[이미지 사이즈]/[유니크한 이미지 이름]
Axios
브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리를 말한다.
axios를 사용하면 서버와의 HTTP 요청을 쉽게 만들고 처리할 수 있다.
모르는 단어가 너무 많기 때문에 하나하나 이해하고 넘어가려고 한다.
- 비동기: 코드의 실행 흐름과 상관 없이 별도로 동작하는 작업
- 네트워크 요청: 서버로 데이터 요청이나 API를 호출하는 작업은 시간이 걸리므로 비동기로 처리
- 파일 읽고 쓰기: 파일을 읽거나 쓰는 작업을 동기적으로 처리하면 블로킹될 수 있기에 비동기로 처리 (앞에서 읽고 쓸 때까지 기다리면 다음 작업을 할 수가 없고, 이러면 대규모나 동시에 뭘 해야 하는 어플리케이션의 경우 문제가 돼서 비동기로 처리해 다른 작업과 병렬로 처리할 수 있게 한다.)
- 타이머 함수: setTimeout, setInterval 같은 함수는 지정된 시간이 경과한 후 작업을 실행하기에 비동기 작업이다.
- 이벤트 핸들링: 사용자의 입력이나 클릭 같은 이벤트 (동기적으로 처리하면 이벤트 핸들러가 끝날 때까지 다음 이벤트를 할 수 없다. 또 여러 이벤트가 동시에 발생할 때 비동기가 아니면 처리할 수 없다.)
- Promise API: JS에서 비동기 작업의 결과를 나타내는 객체
- 대기(pending) - 비동기 작업 진행중
- 이행(fulfilled) - 비동기 작업이 성공적으로 완료되어 결과값을 반환한 상태
- 거부(rejected) - 비동기 작업이 실패하거나 오류가 발생한 상태
- 아래 코드를 보면 fetchData 함수는 setTimeout이라는 비동기 작업을 수행하는데, 성공한다면 resolve를 호출하고 그렇지 않다면 reject를 호출한다.
- .then(): Promise가 성공적으로 해결될 때 실행되는 콜백함수를 등록한다.
- .catch(): 실패했을 때 실행되는 콜백함수를 등록한다.
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John Doe', age: 25 };
if (data) {
resolve(data); // 성공적으로 데이터를 받아온 경우 resolve 호출
} else {
reject('Data not found'); // 데이터를 받아오지 못한 경우 reject 호출
}
}, 2000); // 2초 후에 비동기 작업이 완료됨을 가정
});
}
// Promise 사용 예시
fetchData()
.then((data) => {
console.log('Received data:', data);
})
.catch((error) => {
console.error('Error:', error);
});
- axios: JS에서 주로 사용되는 HTTP 클라이언트 라이브러리로 서버와의 HTTP 요청을 쉽게 만들고 처리할 수 있다.
- 설치 방법: npm install axios --save
- axios 라이브러리를 프로젝트에 설치하고 필요한 파일에서 import || require를 사용해 axios를 가져온다.
- HTTP 요청을 보내기 위해 aixos의 메서드를 사용한다. (GET, POST, PUT, DELETE...)
- 요청에 필요한 URL, 헤더, 매개변수, 본문 데이터를 설정해 axios 요청 메서드를 호출하고 반환되는 Promise 객체를 사용해 비동기 작업의 결과를 처리한다.
axios.get('/api/users')
.then(response => {
// 성공적인 응답 처리
console.log(response.data);
})
.catch(error => {
// 요청 실패 처리
console.error(error);
});
/api/users URL로 GET 요청을 보낸 후 성공적인 응답을 받으면 데이터를 콘솔에 출력하고, 그렇지 않으면 에러를 출력한다.
axios의 메소드나 정확한 사용법을 구체적으로 이해하진 못했지만 금번 프로젝트 후 복습해보면서 다시 알아보고자 한다. 🥹
import axios from "axios";
const instance = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: "[API Key]",
language: "ko-kr",
},
});
export default instance;
수업에 사용한 axios 코드다.
HTTP 요청을 보내기 위해 axios 인스턴스를 생성했다.
(axios.create() 메서드로 새로운 axios 인스턴스를 만들고, 이 인스턴스에 instance라는 변수 할당)
baseURL은 API 요청의 기본 URL이며, params는 요청에 포함될 매개 변수를 설정한다. (잘 모르겠음)
instance는 설정된 baseURL과 params를 가지고 있는 axios 인스턴스로,
이놈을 사용해서 해당 사이트의 API에 대해 HTTP 요청을 보낼 수 있다.
const requests = {
fetchNowPlaying: "movie/now_playing",
fetchNetflixOriginals: "/discover/tv?with_networks=213",
fetchTrending: "/trending/all/week",
fetchTopRated: "/movie/top_rated",
fetchActionMoives: "/discover/movie?with_genres=28",
fetchComedyMovies: "/discover/movies?with_genres=35",
fetchHorrorMovies: "/discover/movies?with_genres=27",
fetchRomanceMovies: "/discover/movies?with_genres=10749",
fetchDocumentarues: "/discover/movies?with_genres=99",
};
export default requests;
request는 강사님이 별도로 다 찾아오셨는데 여기 있는 객체별로 정보를 끌어올 수 있다.
const request = await axios.get(requests.fetchNowPlaying);
이런 식으로 GET 메서드를 써서 정보를 가져오는 것으로 이해했다.
여기까지 이해하는 것만으로도 반나절이 걸렸다. :) fxxx!!!!!!!!!!!!
2. 넷플릭스 전체 구조 만들기
이런 식으로 구조를 나누었다. 오늘은 Banner 까지 만들었다.
네비게이션 바
먼저 왼쪽과 오른쪽에 넷플릭스 로고 / 유저 프로필 이미지를 삽입한다.
이번에 만들려는 페이지의 경우 스크롤을 내리면 전체 화면이 검정색으로 바뀌는데 이 부분을 아래와 같이 클래스에 추가해주었다.
<nav className={`nav ${show && "nav__black"}`}>
<img
alt="Netflix logo"
src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/640px-Netflix_2015_logo.svg.png"
className="nav__logo"
onClick={() => window.location.reload()}
/>
<img
alt="User logged"
src="https://pbs.twimg.com/media/FPOMFrwaQAAOWSc?format=jpg&name=medium"
className="nav__user"
/>
</nav>
생긴 게 삼항연산자랑 비슷해서 그렇게 써도 되는 거 아닌가? 더 복잡해 보이는데? 라고 생각했는데,
이미 nav라는 클래스 명이 있어서 그 스타일이 적용되어 있는 상태에, show가 true일 경우 nav__black도 더해주기 위함이다.
const [show, setShow] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
if (window.scrollY > 50) {
setShow(true);
} else {
setShow(false);
}
});
return () => {
window.removeEventListener("scroll", () => {});
};
스크롤을 아래로 내리는 이벤트가 발생할 때, 50 이상이라면 setShow를 true로 바꿔 nav__black을 적용시킨다.
그렇지 않다면 그냥 false 그대로 두고, 리턴할 때에는 해당 이벤트 리스너를 지워준다.
왜 지우는지 궁금했는데 그렇다고 한다.
구체적으로 이해하진 못했지만 일단 컴포넌트가 끝나면 지워주는 게 좋다는 것 같다.
(useEffect: 2개의 인자를 가지며 한 번만 실행하고 싶은 코드, 의존성 배열이 인자로 쓰인다.)
CSS는 강사님이 만들어서 던져주셨는데 이해 안되는 부분이 좀 있다.
object-fit / z-index 에 대해서도 HC 강의를 들어봐야겠다... 🥲
3. 이미지 배너 만들기
여러 영화를 받아오되 랜덤으로 하나의 이미지만 보여지면 된다.
위에서 말한 것처럼 useEffect를 쓰는데 처음 이미지를 첫 번째 인자로 가지고, 두 번째 인자로 빈 배열을 넣는다. (한 번만 실행할 거임)
const [movie, setMovie] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
// 현재 상영중인 여러 편의 영화 정보 가져오기
const request = await axios.get(requests.fetchNowPlaying);
// 여러 편의 영화 중 하나의 영화 ID 가져오기
const movieId =
request.data.results[
Math.floor(Math.random() * request.data.results.length)
].id;
// 특정 영화의 상세 정보 가져오기
const { data: movieDetail } = await axios.get(`movie/${movieId}`, {
params: { append_to_response: "videos" },
});
setMovie(movieDetail);
};
- async 함수 fetchData: 비동기적 작업을 수행하며 axios 라이브러리를 사용해 API 요청을 처리하고 데이터를 가져온다.
- const request = await axios.get(requests.fetchNowPlaying);
- axios.get() 함수를 사용해 requests.fetchNowPlaying에 정의된 API 엔드포인트로 GET요청을 보낸다.
- await 키워드를 사용해 응답을 기다리고, 응답해 받은 데이터를 request 변수에 저장했다.
- request.data 부분을 보면 20개의 영화를 받아오는데, 그 중 랜덤으로 한 가지 영화의 id 값이 필요하다.
- 아래 axios.get()에서 특정 영화의 상세 정보를 가져올 때 movie/${movieId} 형식으로 요청해야 하기 때문이다.
- console.log로 request.data를 찍어보면 results에 20가지 영화가 있고, 그 안에 id가 들어 있다.
- 랜덤으로 id를 추출해야 하기 때문에 Math.random을 사용한다.
- const { data: movieDetail }은 객체 비구조화 할당을 사용했다.
- axios.get()의 응답은 객체 형태인데, 우리가 필요한 데이터는 data 속성에 담겨 있다. 이를 추출해 movieDetail에 할당한다.
- await 키워드로 비동기 작업이 완료될 때까지 (요청 - 정보를 받아오기) 기다린 후 다음 코드를 실행한다.
- 첫 번째 인자에서는 URL 문자열을 전달하고, 두 번째 인자에서는 요청에 필요한 옵션 객체를 전달한다.
- params 여기는 요청 URL에 쿼리 매개변수를 추가하기 위함이다.
(뭔소린지 모르겠다!)
- params 여기는 요청 URL에 쿼리 매개변수를 추가하기 위함이다.
const { data: movieDetail } = await axios.get(`movie/${movieId}`, {
params: { append_to_response: "videos" },
});
그렇게 받아온 값을 movieDetail로 넣어준다.
개괄적인 UI를 짜주면 된다. (영화 이름의 경우 title, name, original_name 3가지를 적어준다.)
줄거리의 경우 어떤 영화는 짧아서 다 보이고, 어떤 영화는 아주 길다.
이럴 때 저런 식으로 ... 하고 짤리게 만들어주어야 한다.
const truncate = (str, n) => {
return str?.length > n ? str.substr(0, n - 1) + "..." : str;
};
<h1 className="banner__desc">{truncate(movie.overview, 100)}</h1>
배너 설명을 적어주는 부분에 truncate라는 메서드를 넣어준다.
movie.overviwe는 받아온 데이터에 있는 부분으로 줄거리 역할을 해준다.
이 메서드는 str의 길이가 n보다 클 경우 0부터 n까지 잘라주고, 그 뒤를 ... 으로 이어 붙여준다.
n보다 크지 않다면 그냥 그대로 보여주면 되고, 우리는 100자로 제한해두었다.
4. Styled Components 사용하기
Styled Components를 드디어 써보는군!
이건 JS 안에서 CSS를 처리할 수 있게 해주는 대표적인 라이브러리다.
npm install --save styled-componets를 입력하면 설치된다.
styled-components: Basics
Get Started with styled-components basics.
styled-components.com
styled를 쓰고 html 태그를 써주되 ``안에 CSS 코드를 짜주면 된다.
const Iframe = styled.iframe`
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.65;
border: none;
&: :after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%
height: 100%
}
`;
이런 식으로 짤 수 있다.
(가상 요소를 왜 사용하는지 질문을 남겨두었다. - Iframe 위에 새로운 레이어를 생성해서 그런 건가?)
위 이미지에 있는 Play 버튼을 누르면 동영상이 나와야 한다.
버튼을 누르고 말고 여부를 [isClicked, setIsClicked] = useState(false)로 해두었다.
그래서 버튼을 누르면 Onclick={() => setIsClicked(true)} 로 바뀌고, true일 때와 false일 때를 나누어 코드를 작성한다.
true라면 동영상이 나와야 한다.
else {
return (
<Container>
<HomeContainer>
<Iframe
width="640"
height="360"
src={`https://www.youtube.com/embed/${movie.videos.results[0].key}?controls=0&autoplay=1&loop=1&mute=1&playlist=${movie.videos.results[0].key}`}
title="YouTube video player"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen
></Iframe>
</HomeContainer>
</Container>
);
}
이런 식으로 동영상 소스를 가져온다. 자동재생, 풀 스크린으로 적용된다.
앞위로는 styled components로 만든 Container, HomeContainer가 있다.
false일 경우에는 보통과 똑같이 나오는데... 데이터 중에 동영상이 없는 것들도 있다.
그런 것들은 누르면 key 어쩌고저쩌고 오류가 뜨는데 이를 방지하기 위해 동영상이 없다면 버튼을 없애버렸다.
<div className="banner__buttons">
{movie.videos && movie.videos.results.length > 0 ? (
<button
className="banner__button play"
onClick={() => setIsClicked(true)}
>
Play
</button>
) : null}
<button className="banner__button info">More Information</button>
</div>
API 받아오는 부분이 익숙하지 않고, 아직 모르는 개념이나 어려운 용어들이 많아 여기까지 오는데에도 시간이 많이 걸렸다. ㅠ_ㅠ
밑에 부분은 이제 row라고 다른 영화들 큐레이션 부분으로 채워질 예정이다.
본격적으로 데이터를 받아오면서 JS 의 필요성을... 절실히 느낀다...
'⚛️ React > 💭 따라하며 배우는 React A-Z' 카테고리의 다른 글
[React] 넷플릭스 웹페이지의 모달을 구현해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
---|---|
[React] 넷플릭스 웹페이지의 영화를 나열하고 footer를 만들자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] Hooks / TailWindCSS / 라이브러리를 활용해 메모 앱을 업그레이드 해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.25 |
[React] 리액트로 to do 앱을 만들어보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.24 |
[React] 리액트에 대해 알아보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.24 |