1. useParams로 영화 상세 페이지 구현
검색 페이지에서 검색어에 해당되는 영화가 나오는데, 이제 그 영화를 클릭했을 때 영화 포스터가 나오게 하고 싶다.
사실 영화를 클릭했을 때 이런 저런 정보가 나오면 좋겠지만 일단 강의에서는 포스터만 나오게 해두었다.
과연 내가 복습하면서 기능을 추가할 수 있을지 의문이긴 하다 ㅋㅋ.....
아무튼 useParams로 영화 id를 가져오려고 한다.
전 게시글에서 SearchPage를 구현할 때 검색 결과가 있다면 map으로 영화를 꺼내오게 만들었다.
그렇게 나온 이미지 부분을 클릭하면 그 이미지가 마치 다음 페이지에 나온 것처럼 보여주기 위해 navigate를 썼다.
{searchResults.map((movie) => {
if (movie.backdrop_path !== null && movie.media_type !== "person") {
const movieImageUrl =
"https://image.tmdb.org/t/p/w500" + movie.backdrop_path;
return (
<div className="movie" key={movie.id}>
<div
className="movie__column-poster"
onClick={() => navigate(`/${movie.id}`)}
>
<img
className="movie__poster"
src={movieImageUrl}
alt="movie"
/>
</div>
</div>
);
}
이렇게 하면 App.js에 있는 Route path=":movieId" 부분에 이어진 컴포넌트가 DetailPage라 그쪽으로 이동하게 된다.
주소창을 보면 557로 moveId가 557인 영화를 따라 왔다.
하지만 아직 영화 포스터가 보이지 않는다.
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "../../api/axios";
export default function DetailPage() {
const { movieId } = useParams();
const [movie, setMovie] = useState({}); //영화 ID 하나에 대한 정보라 객체로 받아주기
useEffect(() => {
async function fetchData() {
const request = await axios.get(`/movie/${movieId}`);
setMovie(request.data);
}
fetchData();
}, [movieId]); //movieId가 바뀔 때마다 새로 업데이트
useParams로 movieId 값을 가져왔다.
클릭한 영화 하나에 대한 정보를 받아온다. 콘솔에 request.data를 찍으니 아래와 같이 객체가 나온다.
useEffect로 movieId가 변경될 떄마다 fetchData 함수를 실행시키고, 가져온 데이터는 setMovie(request.data)로 업데이트 된다.
if (!movie) {
return <div>...loading</div>;
}
return (
<section>
<img
className="modal__poster-img"
src={`https://image.tmdb.org/t/p/original/${movie.backdrop_path}`}
alt="poster"
/>
</section>
);
}
만약 영화가 없다면 로딩중이라는 문구를 띄워준다.
검색 결과와 무관하게 일단 section UI에서는 이미지가 그대로 나오고, 영화 이미지를 API로 받아온다.
2. 모달 창 외부를 클릭해도 모달 창이 닫히게 하자
현재 모달 창의 우측 상단 ❌ 버튼을 눌러야 모달이 닫히고 있다.
버튼이 작기도 하고 아무데나 눌러도 닫히면 더 편할 테니 아무 데나 눌러도 닫히는 기능을 구현해보자.
어디를 클릭했는지 모달 창 안팎을 구분하기 위해 useRef를 사용한다.
모달 창 밖을 클릭하면 callback 함수를 호출하는 이벤트를 등록하고, 그 함수 안에서 모달을 닫아주면 된다.
useRef는 특정 DOM을 선택할 때 사용하는 리액트 훅스다.
JS에서는 특정 DOM을 선택하려면 getElementById나 querySelector 같은 DOM selector 함수로 DOM을 선택해야 했다.
리액트에서는 ref를 써서 선택할 수 있는데, 지금처럼 함수형일 때는 useRef를 쓴다고 한다. (클래스는 React.createRef)
useRef()로 Ref 객체를 만들고, 이 객체를 컴포넌트의 DOM 요소에 ref 속성으로 할당한다.
그러면 ref.current로 해당 DOM 요소에 직접 접근할 수 있게 된다.
이런 경우는 엘리먼트의 크기를 가져오거나 스크롤바의 위치를 가져오거나 엘리먼트에 포커스를 둘 때 사용한다고 한다.
hooks 폴더에 useOnClickOutside.js를 생성했다.
import React, { useEffect } from "react";
const useOnClickOutside = (ref, handler) => {
useEffect(() => {
const listener = (event) => {
console.log("ref", ref.current);
if (!ref.current || ref.current.contains(event.target)) { //이해안됨
return;
}
handler(); //setModal(false) 부분
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, []);
};
export default useOnClickOutside;
ref는 클릭 영역을 감지할 DOM 요소의 참조고, handler는 클릭 이벤트가 발생했을 때 진행할 콜백 함수다.
useEffect를 써서 이벤트 리스너를 등록하고, 컴포넌트가 시작될 때 한 번 실행한다.
이벤트 리스너는 마우스 다운(마우스 누를 때)과 터치 스타트(터치 디바이스에서 손으로 화면을 터치할 때) 2개고, 진행될 때 listener 함수가 호출된다.
이때 ref.current가 null이거나 클릭된 요소가 ref를 포함하고 있다면 함수는 그냥 종료된다. (아무 반응 없는 거임. 모달 안을 눌러도 아무 반응 없음 == 클릭된 요소가 ref를 포함하니까) 그렇지 않으면 handler 함수가 실행되고 함수가 실행되면 setModal은 false가 되면서 닫히게 된다.
3. Swiper 라이브러리
이전에 왼쪽 오른쪽 화살표를 눌러서 innerWidth를 조절해 영화들을 보여주었다면 이제는 스와이퍼로 하려고 한다.
npm install swiper --save로 설치한다.
Swiper React Components
Swiper is the most modern free mobile touch slider with hardware accelerated transitions and amazing native behavior.
swiperjs.com
사용할 때는 사용될 범위에 <Swiper> </Swiper> 를 적어주고, 실제로 슬라이드를 돌려줄 부분을 <SwiperSlide> </SwiperSlide> 로 감싸줘야 한다. (또 여기도 나열되기 때문에 key를 설정하면 오류가 안 뜬다.)
아래와 같이 쓸 건데, 사용한 모듈에 대해 간단히 설명한다.
Navigation: 이전/다음 버튼을 표시하고 이를 클릭해서 슬라이드를 이동하게 한다.
Pagination: 페이지의 인디케이터를 클릭할 수 있게 하는 옵션이다.
loop: 슬라이드가 무한으로 돌아간다. false로 하면 20편이 다 보여졌을 때 거기서 더 돌아가지 않는다.
breakpoints: 미디어 쿼리를 사용해 반응형 웹을 구현하는 옵션이다. 해당 px일 때 한 화면에 몇 개의 슬라이드가 보일 것이며 슬라이드 한 번에 몇 개의 영화가 넘어갈지를 설정해준다.
modules={[Navigation, Pagination, Scrollbar, A11y]}
navigation
pagination={{ clickable: true }}
loop={true}
breakpoints={{
1378: {
slidesPerView: 6,
slidesPerGroup: 6,
},
998: {
slidesPerView: 5,
slidesPerGroup: 5,
},
625: {
slidesPerView: 4,
slidesPerGroup: 4,
},
0: {
slidesPerView: 3,
slidesPerGroup: 3,
},
}}
배포 부분은 나부터가 못 하고 있어서 오늘 이후에 업로드하겠다...
![](https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif)
'⚛️ React > 💭 따라하며 배우는 React A-Z' 카테고리의 다른 글
[React] TDD로 간단한 어플리케이션 만들기 (0) | 2023.07.14 |
---|---|
[React] TDD (Test Driven Development) (0) | 2023.07.14 |
[React] 넷플릭스 웹페이지에 검색 페이지를 구현하자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] React-Router-DOM과 API에 대해 알아보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] 넷플릭스 웹페이지의 모달을 구현해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |