[React] 넷플릭스 클론 코딩 (6)

노유성·2023년 5월 13일
0
post-thumbnail

⭐React Router Dom 적용하기

🪐페이지 생성을 위한 폴더 추가


다음과 같이 파일 구성을 해준다. 우리는 이전까지 만들었던 메인페이지를 포함해서 2가지의 페이지를 더 구성한 이후 react-router-dom을 이용해서 페이지를 index.html에 렌더링해줄 예정이다.

이전까지 필요한 컴포넌트들을 components 폴더 하위에 구성하였고 이제 실제로 사용자에게 컴포넌트들을 보여줄 때(페이지를 보여줄 때) 이용할 js파일은 pages 폴더 하위에 구성한다. 각각의 페이지들마다 pages폴더 하위에 새로운 폴더를 생성해 그 아래 페이지를 보여줄 index.js 및 css파일을 구성한다.

🪐index.js

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>

);

위와 같이 index.js파일을 변경한다. 렌더링할 App 컴포넌트(최상위 컴포넌트)를 BrowserRouter 컴포넌트로 감싸준다.

🪐App.js

const Layout = () => {
  return (
    <div>
      <Nav />

      <Outlet />

      <Footer />
    </div>
  )
}

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path='/' element={<Layout />}>
          <Route index element={<MainPage/>}/>
          <Route path=":movieId" element={<DetailPage />}/>
          <Route path='search' element={<SearchPage />}/>
        </Route>
      </Routes>
    </div>
  );
}

export default App;

우리는 nested routes를 통해서 중첩된 UI를 레이아웃해서 보여줄 것이기 때문에 layout컴포넌트를 만들어서 사용한다. 왜냐하면 사용자에게 보여줄 3가지 화면 모두 nav바와 footer를 갖고 있기 때문이다.

nav바와 footer를 위와 아래에 두고 사이에 Outlet 컴포넌트를 놨둔다. Outlet 컴포넌트에는 하위 경로들이 표시된다. 위 구조에서는 index route인 MainPage가 Outlet에 표시된다.

🌈경로 매개변수, useParams()

:movieId와 같이 콜론(:)으로 시작하는 표현은 React Router에서 "경로 매개변수" 또는 "동적 경로 매개변수"라고 지칭됩니다. 이는 경로의 일부를 변수로 사용하고 해당 변수에 동적인 값을 매칭시키는 데 사용됩니다.
-chatGPT

나중에 useParams() hook을 이용해서 현재 경로에 대한 정보를 가져올 수 있다. 예를 들어 사용자가 /1234로 DetailPage에 접근했다면 useParams()를 통해 접속한 경로의 값을 가져올 수 있다.

위에서는 경로 매개변수를 movieId로 접근했기 때문에 다음과 같이 사용자가 접속한 url을 확인할 수 있다.

const { movieId } = useParams()

🪐MainPage/index.js

export default function MainPage() {
    return (
        <div>
            <Banner />
            <Row
                title="NETFILX ORIGINALS"
                id="NO"
                fetchUrl={requests.fetchNetflixOriginals}
                isLargerRow />
            <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="NETFILX ORIGINALS"
                id="CM"
                fetchUrl={requests.fetchComedyMovies} />
        </div>
    )
}

배너와 영화 목록들을 구성하는 부분이다. 메인 페이지에서 보여줘야할 내용이기 때문에 MainPage 폴더 내에 위 내용을 구성한다.(기존에는 nav바,배너,row들, footer가 전부 app.js에 정의되었다.)

⭐검색 페이지 구현

🪐Nav.js

🌠input 태그 생성

Nav바에 검색폼을 만들 것이기 때문에 NavBar에 검색 input을 생성한다.
css 스타일링도 Nav.css에 해준다.

<input value={searchValue} onChange={handleChange} className='nav__input' type='text' placeholder='영화를 검색해주세요.'/>
.nav__input {
    position: fixed;
    left: 50%;
    transform: translate(-50%, 0);
    background-color: rgba(0, 0, 0, 0.582);
    border-radius: 5px;
    color: white;
    padding: 5px;
    border: none;
}

🌠searchValue state

const [searchValue, setSearchValue] = useState("");

input 폼에서 값을 입력하면은 사용자의 화면에 즉각적으로 렌더링 해주고 그 값을 이용해서 검색 페이지로 이동하기 위해서 searchValue state변수를 만든다.

🌠handleChange()

const navigate = useNavigate();
const handleChange = (e) => {
   setSearchValue(e.target.value);
   navigate(`/search?q=${e.target.value}`);
}

input 태그에 등록한 이벤트 리스너이다. setter 함수를 이용해서 searchValue를 바꾸고 이를 즉시 렌더링한다. 또한 navigate를 이용해서 특정 경로로 이동하게 한다.

이렇게 되면 사용자가 input 태그에 특정 값을 입력하면은 navigate 함수에 의해서 특정 페이지가 로드된다. 즉 MainPage에서 SearchPage로 바뀐다. 하지만 Nav바는 search 상위 경로인 Layout 컴포넌트에 정의되어 있기 때문에 사라지지 않는다.

🪐SearchPage/index.js

🌠searchTerm 가져오기

searchTerm이란, mainpage에서 searchpage로 넘어올 때 query문도 같이 넘어오는데 query부분을 searchTerm이라고 한다.

const useQuery = () => {
  return new URLSearchParams(useLocation().search);
}

let query = useQuery();
const searchTerm = query.get("q");

useQuery()는 현재 페이지에서 query문의 내용을 가지고 있는 객체를 반환하는 함수이고 해당 객체에서 get메소드를 통해서 query string을 가져온다.

🌠새로운 영화 데이터 가져오기

const [searchResults, setSearchResults] = useState(second)
const fetchSearchMovie = async (searchTerm) => {
  try {
    const request = await axios.get(
      `/search/multi?include_adult=false&query=${searchTerm}`
    )
    setSearchResults(request.data.results);
  } catch (error) {
    console.log("error")
  }
}

인자로 searchTerm을 받아서 axios 요청을 보내는 함수이다. try, catch문을 통해서 axios 요청이 실패했을 때를 처리해주고 있다.

그리고 DB로부터 받은 값을 저장할 state 변수도 선언했다. 요청을 보내고 받은 후에는 setter 함수를 통해서 searchResults 변수에 넣어준다.

useEffect(() => {
  if (searchTerm) {
    fetchSearchMovie(searchTerm)
  }
}, [searchTerm]);

useEffect를 통해서 fetchSearchMovie를 의존성 배열내에 있는 변수인 searchTerm이 변경될 때마다 실행하도록 하고 있다.

searchTerm은 state 변수이기 때문에 어차피 수정되면 자동으로 fetchSearchMovie함수가 실행되지만 fetchSearchMovie()는 비싼 자원이므로 그 외에는 실행되지 않아야 한다(다른 state 변수가 변경될 때). 그러므로 useEffect()에 fetchSearchMovie를 넣어 searchTerm이 변경될 때만 실행되게 한다.

🌌정리하며

component를 사용하는 이유가 재활용을 위해서인데 이렇게 페이지를 로드할 때도 nested routes를 이용해서 자원을 아낄 수 있다는 점이 좋았다.

++

검색창 UI를 구현하기 전에 로고를 클릭해도 반응이 없던 이유는

nav바가 footer에게 덮혀서 클릭을 해도 반응이 없던 거였다. 이벤트 리스너가 반응을 하지 않으면 레이아웃이 겹치지는 않았는 지를 살펴보면 좋을 것 같다.

profile
풀스택개발자가되고싶습니다:)

0개의 댓글