우리가 영화정보를 가져오면 a태그로 선택한 영화를 클릭하여 moive라는 페이지로 이동할 수 있게 만들었습니다.
이번에는 영화의 상세 정보를 확인할 수 있는 페이지를 만들어 보겠습니다.
상세 페이지로 이동을 했을 때 주소 부분에 query string 정보를 통해서 해당 영화의 id값을 통해 보여줄 수 있도록 만들어야 합니다.
우선 상세 페이지로 이동을 했을 때 영화의 정보를 가져 올 수 있는 함수를 만들어 보겠습니다.
store 폴더에 moive.js 파일을 보면
const store = new Store({
searchText: "",
page: 1,
pageMax: 1,
movies: [],
loading: false,
message: "Search for the moive title!",
});
이러한 상태들이 작성되어 있는데 전부 다 영화의 목록을 출력하는 용도였습니다.
그래서 이번에는 moive 라는 상태를 가져와서 빈 객체로 초기화 해주고 해당 부분에는 영화의 상세 정보를 가지고 와서 데이터를 채워 넣도록 하겠습니다.
const store = new Store({
searchText: "",
page: 1,
pageMax: 1,
movies: [],
movie: {},
loading: false,
message: "Search for the moive title!",
});
이렇게 상태를 만들어 놓은 뒤 함수를 한번 작성해보겠습니다.
export const getMovieDetails = async id => {
try {
const res = await fetch(`https://omdbapi.com?apikey=?&i=${id}&plot=full`)
store.state.movie = await res.json();
} catch (error) {
console.log("getMovieDetails error:", error);
}
};
이번 함수에서는 id를 매개변수로 받아 fetch 함수를 통해서 omdbapi에 id의 값과, 줄거리 내용을 받아온 후 store.state.moive에 응답 데이터를 저장합니다.
그다음 우리는 이것을 Home 그러니까 메인 페이지에서 보여주는 컴포넌트가 아니고, 새로운 상세 정보 페이지라는 장소에서 보여줘야 하기 때문에 이부분을 작성해보겠습니다.
index.js
import { createRouter } from "../core/heropy";
import Home from "./Home";
import Movie from "./Movie";
export default createRouter([
{ path: "#/", component: Home },
{ path: "#/movie", component: Movie }
]);
이렇게 새로운 페이지를 연결해주고 난 후에는 어떻게 해야 할까요?
바로 해당 페이지 컴포넌트를 만들어 줘야 합니다. 그전에 우선 MovieItem.js 파일을 한번 살펴봅시다.
import { Component } from "../core/heropy";
export default class MoiveItem extends Component {
constructor(props) {
super({
props,
tagName: "a",
});
}
render() {
const { movie } = this.props;
this.el.setAttribute("href", `#/movie?id=${movie.imdbID}`);
this.el.classList.add("movie");
this.el.style.backgroundImage = `url(${movie.Poster})`;
this.el.innerHTML = /* html */ `
<div class="info">
<div class="year">${movie.Year}</div>
<div class="title">${movie.Title}</div>
</div>
`;
}
}
해당 코드를 보면 우리는 a태그로 만들어 주었고 해당 href 링크를 통해 query string으로 id 파라미터에 실제 그 영화의 값을 포함하여 선택한 영화의 상세정보 페이지로 이동할 수 있게 설계되어 있습니다. 그럼 실제로 해당 페이지를 만들어 봅시다.
routes 폴더에 Moive.js파일을 만들어 줍니다.
import { Component } from '../core/heropy';
import movieStore, { getMovieDetails } from '../store/moive';
export default class Movie extends Componet {
async render() {
await getMovieDetails(history.state.id)
console.log(movieStore.state.movie)
}
}
해당 코드를 살펴보면 우선 movieItem 파일에서 만든 함수 getMovieDetails에서 우리는 특정 영화의 상세정보를 가져올것인데, 그것이 어떤 영화의 ID인지를 확인하고, 해당 id의 영화를 콘솔을 통해 출력하는 코드입니다.
전체적으로 정리를 하자면 우리는 routes폴더에서 만든 index.js 파일로 페이지를 연결시켜 주었고,
MovieItem 파일에서, getMovieDetails 함수를 통해, 영화 정보를 가져오고 상태관리를 통해서, 해당 정보를 다른 컴포넌트에서 사용할 수 있도록 만들었습니다. 이제는 실제로 가져온 상세정보를 화면에 출력하는 과정을 개발해보도록 하겠습니다.
상세정보 페이지에서 보여줘야할 내용들은 무엇일까요? 우선 영화의 포스터, 그리고 줄거리내용, 별점 , 배우, 감독등이 있을 것 같습니다. 이러한 정보들을 가져와서 화면에 출력해보겠습니다.
routes 폴더의 moive.js 파일로 이동해보겠습니다.
movie.js
import { Component } from "../core/heropy";
import movieStore, { getMovieDetails } from "../store/movie";
export default class Movie extends Component {
async render() {
await getMovieDetails(history.state.id);
console.log(movieStore.state.movie);
const { movie } = movieStore.state;
this.el.classList.add("container", "the-movie");
this.el.innerHTML = /* html */ `
<div
style="background-image: url(${movie.Poster});"
class="poster">
</div>
<div class="specs">
<div class="title">
${movie.Title}
</div>
<div class="labels">
<span>${movie.Released}</span>
/
<span>${movie.Runtime}</span>
/
<span>${movie.Country}</span>
</div>
<div class="plot">
${movie.Plot}
</div>
<div>
<h3>Ratings</h3>
${movie.Ratings.map((rating) => {
return `<p>${rating.Source} - ${rating.Value}</p>`;
}).join("")}
</div>
<div>
<h3>Actors</h3>
<p>${movie.Actors}</p>
</div>
<div>
<h3>Director</h3>
<p>${movie.Director}</p>
</div>
<div>
<h3>Production</h3>
<p>${movie.Production}</p>
</div>
<div>
<h3>Genre</h3>
<p>${movie.Genre}</p>
</div>
</div>
`;
}
}
우선 해당 코드를 하나씩 살펴보도록 하겠습니다.
const { movie } = movieStore.state;
우선 movieStore.state에서 movie 객체를 구조 분해 할당하여 필요한 데이터를 가져올 수 있게 했습니다.
this.el.classList.add("container", "the-movie");
this.el.innerHTML = /* html */ `
<div
style="background-image: url(${movie.Poster});"
class="poster">
</div>
<div class="specs">
<div class="title">
${movie.Title}
</div>
<div class="labels">
<span>${movie.Released}</span>
/
<span>${movie.Runtime}</span>
/
<span>${movie.Country}</span>
</div>
<div class="plot">
${movie.Plot}
</div>
<div>
<h3>Ratings</h3>
${movie.Ratings.map((rating) => {
return `<p>${rating.Source} - ${rating.Value}</p>`;
}).join("")}
</div>
<div>
<h3>Actors</h3>
<p>${movie.Actors}</p>
</div>
<div>
<h3>Director</h3>
<p>${movie.Director}</p>
</div>
<div>
<h3>Production</h3>
<p>${movie.Production}</p>
</div>
<div>
<h3>Genre</h3>
<p>${movie.Genre}</p>
</div>
</div>
`;
}
}
해당 코드는 현재 컴포넌트의 el에 2개의 클래스를 추가했는데 이부분은 css 작성을 위해 추가한 것입니다.
그리고 해당 el의 하위 요소로 html을 정의해줬는데 내용들을 살펴보면 아까 화면에 표시하기 위해 필요한 정보들을 실제로 넣어주기 위해 작성된 것들입니다.
영화의 포스터, 제목, 출시일, 줄거리, 평점, 배우목록, 감독이름, 제작사 정보, 장르 정보를 표시하기 위해 작성되어 있습니다.
평점의 경우만 자세히 살펴보자면, Ratings 배열을 반복(map)하여 각 평가의 출처(source)와 값(vaule)의 값을 표시하는 p요소를 생성하도록 만들어 주었습니다.
이제 css를 작성해주면 상세보기 페이지가 만들어집니다.
고해상도의 포스터를 넣기
routes폴더의 movie.js 파일에 render함수에서 객체 구조 분해 할당 코드 밑에 해당 코드를 추가해주시면 됩니다.
const bigPoster = movie.Poster.replace("SX300", "SX700");
<div
style="background-image: url(${bigPoster});"
class="poster">
</div>
스켈레톤 UI로 로딩 애니메이션을 제공을 해보도록 하겠습니다.
그전에 스켈레톤 UI가 무엇인지 알아봅시다. 우선 스켈레톤 UI란 실제 화면에 보여지는 UI들이 렌더링 되기 전 화면에서 보이게 될 윤곽을 그려주는 로딩 애니메이션 입니다. 이를 통해 사용자 경험을 향상시키기 위한 로딩 전략중 하나입니다. 이를 통해 사용자는 현재 로딩 중임을 인지하고 화면상 어떤 데이터들이 렌더링 되는지를 확인하고 보다 친화적으로 받아들일 수 있습니다.