[디즈니] 검색/상세 페이지 구현

프린이·2024년 11월 28일
post-thumbnail

📁파일 구조

index.js 만든 담에 ctrl + c v하고 rafce 함수형 컴포넌트 만들기❗


🤩메인 페이지

🤷‍♀️App.js➡index.js로 옮겨주고 싶은데 어떻게 할까❓

pages>MainPage>index.js

import React from 'react'
import Nav from '../../components/Nav'
import Banner from '../../components/Banner'
import Category from '../../components/Category'
import Row from '../../components/Row'
import requests from '../../api/requests'
import styled from 'styled-components'

const MainPage = () => {
  return (
    <Container>
      <Nav />
      <Banner />
      <Category />
      <Row title="Trending Now" id="TN" fetchUrl={requests.fetchTrending} />
      <Row title="Top Rated" id="TR" fetchUrl={requests.fetchTopRated} />
      <Row title="Action Movies" id="AM" fetchUrl={requests.fetchActionMovies} />
      <Row title="Comedy Movies" id="CM" fetchUrl={requests.=fetchComedyMovies} />
    </Container>
  )
}
export default MainPage

App.js

import { Route, Routes } from "react-router-dom";

const Layout = () => {
  return (
    <div>
      {/* 메인에서 항상 보여줘야 되는 부분 : nev */}
      <Nav />
      {/* 자식 경로 요소 렌더링하려면❓
		  👉 부모 경로 요소에서 Outlet 태그 사용해야 함❗
		  이렇게 하면 하위 경로가 렌더링될 때
          중첩된 UI 표시될 수 있음 */}
      <Outlet />
    </div>
  )
}

function App() {
  return (
    {/* 1. Routes로 먼저 감싸주기 */}
    <Routes>
      {/* 2. Route로 감싸고 여러개의 Route 넣을 것(중첩) */}
      {/* localhost:3000번에 왔을 때 Layout 렌더링 될 때
      	LoginPage도 같이 렌더링 되게 하고 싶으면➡index */}
      <Route path="/" element={<Layout />}>
      	<Route index element={<LoginPage />} />
        <Route path="main" element={<MainPage />} />
        <Route path=":movieId" element={<DetailPage />} />
        <Route path="search" element={<SearchPage />} />
      </Route>
    </Routes>
  );
}
export default App;

💁‍♀️localhost:3000/main으로 왔을 때
MainPage 컴포넌트가 Nav 밑으로 오게 하려면➡Outlet 컴포넌트 사용

실행하면 Nev랑 loginPage 뜨는 것을 확인할 수 있음👀


🔍검색 페이지

⌨타이핑 할 때 SearchPage로 넘어가게 할 것

🕸spiderman 검색해서 뜨고 여기서 아무거나
이미지 클릭하면 해당 이미지의 상세페이지가 나옴

input창 만들기 - components>Nav.js

Logo 밑에 만들면 되겠져❓

input styled 컴포넌트 작성하기

input창이 오른쪽에 있음

const Input = style.input`
	position: fixed;
	left: 50%;  // 이렇게 하면 정중앙일까❓
	// input 창의 왼쪽 기준으로 정렬 되어서 아님🙅‍♀️
	// input 전체의 반 만큼만 옮겨주면 됨❗
	transform: translate(-50%, 0);  // 이렇게 하면 정중앙
	background-color: rgba(0, 0, 0, 0.582);
	color: white;
	padding: 5px;
	border: 1px solid lightgray;
`

실행하면 정중앙으로 input창 완성✨

🤔타이핑한게 경로로 가게되고 경로 바뀜에 따라
컴포넌트 렌더링하는 건 어떻게 구현할까❓

첨엔 localhost:3000/main이 었다가 input창에 spiderman 이라고
타이핑 치니까 search?q=spiderman으로 변경되는 것을 확인 할 수 있음👀

value 안에 state 넣고 타이핑할 때마다 state를 업데이트 해주면 됨❗

state 만들어주기

⬇ 타이핑 할 때마다 searchValue를 업데이트 해주는 함수

state도 변경해줘야 되겠지만
더 중요한건 타이핑할 때 위에 부분 ⬇

이런식으로 해줘야겠져❗ 어떻게 할까❓

🤷‍♀️react-router-dom API 중 어떤걸 사용하면 될까❓

👉 useNavigate
( 경로 바꿔줌 navigate('/home')➡localhost:3000/home )

reate-router-dom에서 제공받음

타이핑 한 게 e.target.value에 들어가면서 저 경로로 이동할 수 있는 것

경로가 바뀌니까 바로 다른 search 페이지 컴포넌트도
렌더링 되는 것을 볼 수 있음👀


[ 순서 ]
1 ) spiderman이라는 값을 SearchPage로 가져와야함
2 ) spiderman을 이용해서 요청(The MovieDB API한테 🗣"spiderman하고 관련된 영화 다 보내줘") 보내고 응답 받음
3 ) 응답 받은 걸 state에다 넣어줌
4 ) 그 담에 state가 변경되면 화면에서 보여줌

pages>Searchpage>index.js

1 ) Search 컴포넌트에 spiderman 값 가져오고 싶으면 어떻게 해야할까❓

🙋‍♀️먼저 useParam & useLocation 헷갈릴 수 있음

:movieId
➡localhost:3000/owpkefwkefpwkfwoepf으로 갔다고 했을 때
owpkefwkefpwkfwoepf 이 값을 가져오려면 useParam 사용하면 됨✅

지금은 spiderman 값을 가져와야 해서 useLocation 사용하면 됨✅

즉, useLocation➡url의 전체 정보 가져오는 것
:movieId 다이나믹 하게 되는 이 부분➡useParam

useLocation➡호출하면 객체를 리턴🔁

search에서 spiderman만 가져와야 함
👉 그럴려면 search의 전체 값을 가져와야 함❗

그러면 콘솔에 값이 다 가져와짐

🤷‍♀️?p=는 필요없으니
spiderman만 갖고 오고 싶은데 어떻게 하면 될까❓

가장 간단하게 가져올 수 있는 방법은🔍

Web에서 제공해주는 API 중 URLSearchParams가 있음

👉 이용하면 굉장히 많은 메서드들을 사용할 수 있음✨

ex )

q는 뭐고 topic은 뭔지 되어 있음
👉 이 string을 URLSearchParams에 넣어줌
👉 searchParams가 객체가 됨🙆‍♀️
( 이 객체에서 사용할 수 있는 메서드들이 많음⤴ )

has : string에서 현재 topic이 있는지❓➡있으니 true❗
get : topic의 값은 api가 맞으니까 true⭕
getAll : 모든 걸 다 가져오는 것➡["api"] ( 배열에 가져옴 )
set : topic=More webdev ( ➕추가할 수도 있음 )

이 중에서 어떤 메서드를 이용해야
spiderman을 가져올 수 있을까❓➡ get 💡

console.log(searchTerm); 하고 콘솔 보면 값을 잘 가져옴

2 ) 📢요청➡🗣응답

💁‍♀️useEffect 사용하기

⬇ 요청 보내기 위한 함수 만들기❗

${ } 여기다가 searchTerm 타이핑 한 거 넣어주면 됨

if : searchTerm 값이 있을 때만 호출📢
[searchTerm 변경되면 다시 호출🔄]

3 ) 응답 받은 걸 state에다 넣기

setSearchResults 이용해서 배열에다 넣어줄 것
response.data.results에 필요한 배열 데이터가 있음✅
만약 데이터 요청했는데 에러났을 땐 콘솔에 error 출력🚨

콘솔로 찍어보면

타이핑 칠 때마다 데이터 요청 계속 간 것

네트워크 확인해보면 데이터 짱 많이 보내짐👀
나중에 타이핑 할 때마다 요청 안보내지게 처리할 것🔥

4 ) 💻화면에 보여주기

🕸스파이더맨에 관한 데이터를 보여주려 하는데
데이터가 있을 수도 있고 반환 데이터가 없을 수 있음❗

🙋‍♀️조건문 if~else 처리하기

🙆‍♀️데이터가 있으면 ⬇

검색한 거에 대한 포스터를 보여줘야 하는데
backdrop_pathnull이면 포스터가 없는 것도 있음
없는 영화는 생략하기 위해 씀😎

movie.media_typeperson인 것은
데이터 전달해준 것 중에서 포스터에 대한 걸 보여주는 게 아니라
사람의 이미지 출연진의 이미지를 보여주는 것도 있어서
이런 것들을 빼려고 쓴 것👊

🎬여러 개의 영화 포스터 중
👉 원하는 포스터 클릭 시 해당 포스터의 상세 페이지로 이동시킬 것🐱‍🏍

🙅‍♀️데이터가 없으면 ⬇

🕷🕸

SearchPage.css

포스터에 커서 올리면 커졌다가 빼면 원래 이미지 사이즈로 돌아옴🎶

css 끗👩‍🎨

🚨문제점 : 타이핑 할 때마다 데이터 요청 보내짐😬

🤔Debounce❓

어떤 걸 하나(s) 검색하고 바로 요청 보내서 가져오고
또 하나(p) 검색해서 이에 대한 요청 보내서 가져오고
이렇게 하면 백엔드에서 너무 고된,,,😩

웹에선 굉장히 많은 사람들이 검색을 하는데
그렇기에 Debounce 주로 이용해서
불필요한 로드를 하지 않을 수 있게 해줌🙊👍

🎣커스텀 훅 사용 ⬇

📁hooks 폴더 만들기

hooks>useDebounce.js

input에 타이핑하는 값➡value

useDebounce.js 에서 export 해줬으니
다른 컴포넌트에서 useDebounce 사용 가능🙆‍♀️

기다리는 값 넣어주기➡delay

debouncedValue를 리턴🔁

여기서 useDebounce가 리턴하는 건➡debouncedValue를 리턴🔁

타이핑 하면(s)➡value로 들어와서 debouncedValue의 기본 값으로 됨🙆‍♀️

🤷‍♀️0.5초 기다릴 때는 어떠한 로직을 써야할까❓

🕐어떠한 걸 기다릴 때 쓰는 건➡setTimeout

⏳얼만큼 기다릴 건지❓➡delay 만큼❗

s 타이핑 했다고 가정했을 때
delay 만큼 기다린 다음에 state 타이핑 한 걸 변경해줄 것🔥

searchTerm 아니고 이젠 debouncedSearchTerm으로 요청하면 됨🙆‍♀️


🖱포스터 클릭 했을 때

상세페이지(DetailPage)로 왔는데

여기서는 어떻게 해야 할까❓

영화에 대한 상세한 데이터들을 나열하면 됨❗

데이터가 필요하겠죠❓

👀상세 페이지 - pages>DetailPage>index.js

🎬상세 영화에 대한 데이터 받아오기📥

저 부분을 가져와야 함❗

🤷‍♀️가져오려면 어떻게 해야할까❓

💁‍♀️useParams 이용하기

여기 이름은 왜 movieId 일까❓

App.js 에서 path=":movieId"로 지정해놔서❗

영화 ID 바로 갖고 오려면 { movieId } 이렇게 해야됨✅
👉 이걸 이용해서 데이터 받아오기 - useEffect 사용✨

의존성 배열 : [movieId 바뀌면 다시 호출🔄📢]
movieId 바꼈다는 건➡다른 상세페이지로 갔다는 것을 의미🐱‍🏍
다시 영화의 상세 데이터 가져와야 해서 의존성 배열에 넣어준 것🤩

데이터 받아왔으면 다음으로 항상 할 일은❓
👉 기억해야하므로 state에 넣어주기🔥

⬆ 하나의 영화 상세 데이터

🤷‍♀️데이터 받아오고 state까지 해줬으니까 그다음은❓❓

👉 패턴 : 필요한 데이터 가져오고 state에 넣어주고 화면에 보여주기🌟

첨엔 movie가 null임

💁‍♀️컴포넌트 렌더링 순서

여기가 완성이 되기 전엔 movie는 null❗
null이기 때문에

movie가 아직 null(값없음)이면 "loading..." 화면을 보여준다는 것

데이터 가져왔을 땐 엄청나게 많은 데이터들이 들어있겠지만
이미지 하나만 보여주겠다❗

잘 나오는 걸 확인 할 수 있음👀


➕기능 : 메인페이지 모달창 닫는 거 ✖ 아닌
외부 클릭해도 닫을 수 있게 하기😎

window.document 써도 되는데 좀 더 리액트스럽게 하려면❓

🎣useRef 리액트 hooks 사용하기

useRef➡ 1 ) 변수 관리 2 ) 특정 DOM을 선택 할 때 사용🔥

💁‍♀️리액트 컴포넌트 안에서 어떠한 값을 기억할 수 있는 방법
👉 const/let/var 이용해서 변수 만들기
👉 state 사용하기
👉 useRef 사용하기

🧐특정 DOM을 선택할 때

보통 JS에서 특정 DOM에 접근하려면❓
👉 getElementById, querySelect 같은
DOM Selector 함수 사용해서 DOM 선택❗

리액트 사용 시 useRef 이용해서 특정 DOM 선택 가능🙆‍♀️

useRef➡리액트에서 가져오겠져❓

특정 value를 초기값으로 넣어주면 'ref 객체'가 생성됨🎇

👉 'current' 라는 프로퍼티를 갖고 있는 객체가 생성됨❗

🎄어떠한 특정 DOM 접근하려면❓➡ref={myRef}

👉 요소에 Ref라는 속성에다가 useRef를 이용해서
만든 객체(myRef)를 값으로 넣어주면 됨✅

👊모달창 내부와 외부 클릭했을 때랑 구분을 위해

DOM 노드 저부분을 가져오고 싶음

components>Modal.js

⬆ 저 노드 부분을 가져올 것❗

ref 객체는 { current: null } 이라는 걸 콘솔에서 확인할 수 있음

🤷‍♀️모달 내부 전체를 current: null에
넣어주고 싶은데 어떻게 해야할까❓

ref={ref} 하면 됨

콘솔로 확인해보면 안 들어와 있는데 실제론 들어온 것

억지로 1넣어주고 렌더링 하면

이번엔 잘 나오는 걸 확인할 수 있음

🤷‍♀️왜 그러냐면❓

컴포넌트가 리렌더링 되게 하려면 어떻게 해야할까❓

👉 state가 변경 되면 됨❗

지금은 { current: null }➡{ current: 요소 }로 변경됨 ( 렌더링❌ )

🌟중요한 건
{ current: null } current의 값(null)을 해당 요소를 넣어주려면
ref에다가 useRef로 만든 객체(ref)의 ref를 넣어주는 것🔥

콘솔에 저것들을 이용해서 모달 클릭했을 때
저 요소가 들어있는 곳을 클릭했는지 아니면
요소 바깥을 클릭했는지를 구분할 것 🙆‍♀️✔

🎣hooks 만들기

hooks>useOnClickOutside.js

📢useOnClickOutside 함수를 Modal.js 에서 호출하기

ref 가져오고 ref 바뀔 때마다 다시 호출할 것🔄📢

Modal.js➡모달 닫으려면(✖) 밑줄 친 부분 호출하면 됨📢✔

이름은 handler로 지정해주고
handler 함수 변하더라도 다시 호출하게 해줌🔄📢

🤗터치할 때 뭐를 할 수 있게 '리스너' 등록하기❗

🖱어떠한 걸 클릭할 때

mousedown : 사용자가 해당 element에서 마우스 버튼을 눌렀을 때 발생mousedown 이벤트가 일어나면 listener 함수 작동🙆‍♀️

더 이상 안 쓰이면 unmount 될 때이걸 제거 해야됨⭕

안 쓰일 땐 mousedown 일어나도
listener 함수 호출되지 않게 제거 해야됨🗑❌

🤔어떻게 제거 하면 될까❓

이렇게❗

현재 handler가 저 함수임

💁‍♀️모달의 외부 클릭했을 때 handler 함수 호출하면 됨

✏ 내부 클릭하는지 외부 클릭하는지 조건 적어주기

ref.current가 아무것도 없는 null이라는 건👉 ref={modalRef} 등록 안 해준 것

지금은 등록했으니까 !ref.current는 🆗

ref.current➡modal node

event.target➡클릭한 노드

contains➡포함

👉 ref.currentevent.target 포함하고 있으면 내부를 클릭한 것❗

👉 포함하지 않으면 외부를 클릭한 것❗

if 안엔 내부 클릭할 때 return으로 끝내기

외부 클릭했을 땐 handler 함수 호출해서
( ) => setIsModalOpen(false) 모달 닫아주기🙆‍♀️

😎외부 클릭하면 모달창 닫아지고 내부 클릭하면 아무것도 일어나지 않음

profile
안녕하세요! 퍼블리싱 & 프론트엔드 개발 공부 블로그 입니다!

0개의 댓글