안녕하세요 Lotts 입니다 😉
저번 포스팅에서는
ReactJS 설치와 네이버 검색 API를 어떻게 사용하는지를 확인해봤어요
자세한 내용은 아래 포스팅을 참조하시면 됩니다
2020/03/27 - [프로그래밍/ReactJS] - React로 영화 정보를 검색해보자 - 첫 번째 React 설치 / 네이버 API 사용기
이번 포스팅에서는
✅ 네이버 검색 API를 이용하여 영화 정보 가져오기
✅ CORS를 처리하는 방법 One
✅ CORS를 처리하는 방법 Two
에 대해서 알아보겠습니다
👏👏👏👏
React로 영화정보를 검색해보자 두 번째 시간 🎉
📢네이버 검색 API를 이용하여 영화 정보 가져오기
저번 포스팅에서 영화 정보를 가져오는 방법에 대해서는
POSTMAN를 통해 확인했어요
본격적으로 소스를 보면서 확인해 보겠습니다 🔎
directory 구조
movie_app
|
|
--src
|
--App.js
--index.js
|
--routes
|
--Search.js
|
--components
|
--SearchMovie.js
--Navigation.js
✅index.js
💡<App />는 component 이며 data를 보여줍니다
또한 HTML을 반환하는 함수입니다
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
✅App.js
nomadcorders 인강 마지막 부분에 HashRouter를 사용하여 분기 처리를 해주는데 여기서 저는
Search를 별도로 만들어서 영화 검색에 사용하였습니다 😊
📍 react-router-dom 설치
npm install react-router-dom
react-router-dom을 설치하고 HashRouter를 사용합니다
import React from 'react';
import {HashRouter, Route} from 'react-router-dom';
import Home from "./routes/Home";
import Search from "./routes/Search"
import Navigation from "./components/Navigation";
function App(){
return (
<HashRouter>
<Navigation />
<Route path="/" exact={true} component={Home}></Route>
<Route path="/search" exact={true} component={Search}></Route>
</HashRouter>
);
}
export default App;
💡 참고
Route를 보면 path가 '/', '/search'가 있습니다.
exact={true} 옵션이 없으면 react는 현재 주어진 path를 통해 모든 route를 조회합니다
예를 들어 현재 경로가 'localhost:3000/#/search' 인경우 react는 /#/search에 대해서
모든 route를 검색하는데 해당되는 route는
<Route path="/" component={Home}></Route>
<Route path="/search" component={Search}></Route>
모두 해당이 됩니다.
따라서 두개의 route에 있는 데이터가 렌더링 됩니다
이러한 이슈를 해결하기 위해서는 현재 코딩처럼 exact={true}를 같이 사용합니다
💡 exact={true} 옵션
설정하면 해당 path에 정확히 일해야 렌더링을 진행합니다
따라서 'localhost:3000/#/search' 경우 '/search'만 랜더링을 진행합니다
✅Search.js
네이버 검색 API를 이용하여 영화의 이름을 검색하여 데이터를 받아오는 부분입니다
저번 포스팅에서 보신 것처럼 api를 이용해 데이터를 가져오면 아래 JSON 형식처럼 가져옵니다
따라서 해당 data안에 items리스트를 가져와 합니다 🔎
"items": [ { "title": "<b>아이언맨</b>",
"link": "https://movie.naver.com/movie/bi/mi/basic.nhn?code=123684",
"image": "https://ssl.pstatic.net/imgmovie/mdi/mit110/1236/123684_P00_161340.JPG",
"subtitle": "",
"pubDate": "2014",
"director": "김용수|",
"actor": "이동욱|신세경|김갑수|",
"userRating": "4.94"
},
{ "title": "<b>아이언맨</b> & 캡틴 아메리카",
"link": "https://movie.naver.com/movie/bi/mi/basic.nhn?code=133519",
"image": "https://ssl.pstatic.net/imgmovie/mdi/mit110/1335/133519_P01_183929.jpg",
"subtitle": "Iron Man & Captain America: Heroes United",
"pubDate": "2014",
"director": "",
"actor": "아드리안 패스더|로저 크레이그 스미스|",
"userRating": "9.00"
}
]
전체 소스를 확인하고 한 줄씩 간략하게 설명하도록 하겠습니다
import React from 'react';
import axios from 'axios';
import SearchMovie from '../components/SearchMovie';
import "./Home.css";
import "./Search.css";
class Search extends React.Component {
state = {
isLoading: true,
movies: [],
value: ""
};
getSearchMovie = async () => {
const ID_KEY = 'id_key';
const SECRET_KEY = 'secret_key';
const search = this.state.value;
try {
if (search === "") {
this.setState({movies: [], isLoading: false})
} else {
const {data: {
items
}} = await axios.get('https://openapi.naver.com/v1/search/movie.json',{
params:{
query: search,
display: 20
},
headers: {
'X-Naver-Client-Id': ID_KEY,
'X-Naver-Client-Secret': SECRET_KEY
}
});
this.setState({movies: items, isLoading: false});
}
} catch (error) {
console.log(error);
}
};
componentDidMount() {
this.getSearchMovie();
};
handleChange = (e : any) => {
this.setState({value: e.target.value});
};
handleSubmit = (e : any) => {
e.preventDefault();
this.getSearchMovie();
};
render() {
const {movies, isLoading} = this.state;
return (<section className="container">
{
isLoading
? (<div className="loader">
<span className="loader__text">Loading..</span>
</div>)
: (<form onSubmit={this.handleSubmit}>
<div>
<div className="input_div">
<h1>영화 검색</h1>
<input className="input_search" type="text" value={this.state.value} onChange={this.handleChange} placeholder="영화를 검색해 보세요."/>
</div>
<div className="movies">
{movies.map(movie => (
<SearchMovie key={movie.link} id={movie.link} year={movie.pubDate} title={movie.title} poster={movie.image} rating={movie.userRating} director={movie.director} actor={movie.actor}
/>))
}
</div>
</div>
</form>)
}
</section>);
}
}
export default Search;
💡 전반적인 흐름
react생명주기를 보시면 render() 후에 componentDidMount()가 실행되기 때문에 nomadcorders 식 방법으로 isLoading를 만들어서 처음 render() 시에는 loading 중을 표시하고 이후 componentDidMount()에 있는 getSearchMovie() 함수를 실행하여 영화 정보를 가져와 State를 변경하면 React는 그 내용을 render() 메서드를 다시 호출하여 보여주도록 합니다 (강의를 듣다 보니 니꼴라스쌤이 이런 방식으로 많이 하십니다)
API를 사용하기 위해서는 axios를 설치해야 합니다 😊
📍 axios 설치
npm install axios
네이버 검색 API 가이드를 참조하여 get 방식으로 json데이터를 호출하는 부분입니다 🔎
axios는 가져오는데 시간이 오래 걸리기 때문에 async를 설정하고 누구를 기다려야 하는지 await를 설정합니다
params에 우리가 검색해야 할 영화 정보(search)를 필수로 넣어주며 검색 결과의 건수(display)는 선택 사항입니다
그다음 가장 중요한 절차인 headers에 발급받은 id, secret 값을 넣어줍니다👌👌
const {data: {
items
}} = await axios.get('https://openapi.naver.com/v1/search/movie.json',{
params:{
query: search,
display: 20
},
headers: {
'X-Naver-Client-Id': ID_KEY,
'X-Naver-Client-Secret': SECRET_KEY
}
});
영화 정보를 State에 저장합니다 👍
this.setState({movies: items, isLoading: false});
State에 직접 변경하는 것은 좋지 않습니다 🙅♀️
this.state.movies = items;
input 이벤트인 onchange를 사용하여 검색어를 입력하면 실시간으로 검색 정보(value)를 State에 저장합니다
또한 handleSubmit은 input box에서 엔터를 입력 시 정보가 전송되는 기본 이벤트이며
해당 이벤트가 발생할 때 getSearchMovie() 함수를 실행하도록 하였습니다.
다만 해당 이벤트의 고유 기능은 사용하지 않기에 e.preventDefault()를 사용하여 막아두었습니다 😁
handleChange = (e : any) => {
this.setState({value: e.target.value});
};
handleSubmit = (e : any) => {
e.preventDefault();
this.getSearchMovie();
};
render() 메서드 안에 있는 변경된 State의 값에서 영화 정보(movies) 리스트📜를 map을 이용하여 하나씩 SearchMovie로 렌더링을 진행합니다
render() {
const {movies, isLoading} = this.state;
return (<section className="container">
{
isLoading
? (<div className="loader">
<span className="loader__text">Loading..</span>
</div>)
: (<form onSubmit={this.handleSubmit}>
<div>
<div className="input_div">
<h1>영화 검색</h1>
<input className="input_search" type="text" value={this.state.value} onChange={this.handleChange} placeholder="영화를 검색해 보세요."/>
</div>
<div className="movies">
{movies.map(movie => (
<SearchMovie key={movie.link} id={movie.link} year={movie.pubDate} title={movie.title} poster={movie.image} rating={movie.userRating} director={movie.director} actor={movie.actor}
/>))
}
</div>
</div>
</form>)
}
</section>);
}
💡 주의할 점
렌더링 시 주의해야 할 점은 key가 필요하다는 것입니다.
공식문서에 Key에 대해 자세한 설명이 있습니다
✅SearchMovie.js
Search.js에서 영화 정보를 렌더링 하여 보여주는 화면 부분입니다
전달받은 데이터의 유효성을 검증하기 위해 propType를 사용합니다
역시나 사용하기 위해서 설치를 진행해 줍니다
📍 prop-types 설치
npm i prop-types
전체 소스입니다
import React from "react";
import {Link} from 'react-router-dom';
import PropTypes from "prop-types";
import "./Movie.css";
function SearchMovie({id, year, title, poster, rating, director, actor}) {
return (
<div className="movie">
<a href={id} target="_blank">
<img src={poster} alt={title} titlt={title}></img>
<div className="movie__data">
<h3 className="movie__title">{
title.replace(/<b>/gi,"").replace(/<\/b>/gi,"")
}</h3>
<p className="movie__rating">
<span>평점</span> {rating}/10
</p>
<p className="movie__year">
<span>개봉일</span> {year}
</p>
<p className="movie__director">
<span>감독</span> {director}
</p>
<p className="movie__actor">
<span>배우</span> {actor}
</p>
</div>
</a>
</div>
)
};
SearchMovie.propTypes = {
id: PropTypes.string.isRequired,
year: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired,
rating: PropTypes.string.isRequired,
director: PropTypes.string.isRequired,
actor: PropTypes.string.isRequired
};
export default SearchMovie;
전달받은 데이터를 기본 문법대로 꾸며주시면 됩니다.
💡 주의할 점
propTypes는 반드시 propTypes로 해야 합니다
PropTypes❌
propsTypes❌
안됩니다
만약 다른 이름을 사용하면 아래와 같은 에러가 발생됩니다 🙅♀️
Warning: Component FoodLike declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?
드디어 😁😁
네이버 검색 API를 이용하여 영화 정보를 가져왔습니다
실행화면입니다
👏👏
제가좋아하는 어벤져스를 검색해 보았습니다 😎
전체 소스는 Lotts github에 올렸기 때문에 참조하시면 됩니다
하지만 여기서 끝이 아닙니다 ❗❗
반전이 있지요... 😅😅
바로 위에 처럼 호출을 하면
한 번쯤 겪어 봤을 법한 교차 출처 리소스 공유( Cross-Origin Resource Sharing, CORS )를
보게 되실 겁니다
바로 아래 처럼 말이지요 💦
이거 뭐야? 안되는 걸 알려준 거야? 😤
하시겠지만
이것 때문에 정말 엄청 찾아보고 고생 많이 했습니다 🤣
자 그럼 CORS를 해결하는 방안에 대해
알아보겠습니다 🔎
📢CORS를 처리하는 방법 ONE
아주 간단한 방법부터 보여드리겠습니다
바로 Proxy를 설정하는 방법입니다
✅ package.json
{
"name": "movie_app_2019",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"cors": "^2.8.5",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-scripts": "3.3.0"
},
"proxy":"https://openapi.naver.com"
💡 해당 파일에서 ❛ proxy:"https://openapi.naver.com" ❜을 추가 줍니다
✅ Search.js
const {data: {
items
}} = await axios.get('/v1/search/movie.json',{
params:{
query: search,
display: 20
},
headers: {
'X-Naver-Client-Id': ID_KEY,
'X-Naver-Client-Secret': SECRET_KEY
}
});
💡 axios 변경
❛ https://openapi.naver.com/v1/search/movie.json ❜ 👉👉 ❛/v1/search/movie.json❜ 변경합니다
그러면 설정해둔 proxy로 api가 정상 호출되어 CORS의 문제없이 정보를 가져올 수 있습니다
위의 방식으로
아주 간단히 CORS 문제를 해결할 수 있습니다
👏👏👏
다만 몇 가지 이슈가 있습니다 😎
해당 방식은 서버로 배포나 github page 배포 시에는
CORS 문제를 해결할 수 없으며 개발환경에서만 적용됩니다 💦
또한
여러 개를 호출할 수 없고 1개만 가능합니다 😅
그렇다면 여려 개를 호출할 수 있는 방법은 무엇일까요?
바로 다음 CORS 문제를 해결하는 방법을 알려드리겠습니다 🔎
📢CORS를 처리하는 방법 TWO
두 번째 방법은 Proxy server를 같이 생성하여 사용하는 방법입니다 😁
📍 http-proxy-middleware 설치
npm install http-proxy-middleware --save
src 폴더 아래에 setupProxy.js 파일을 생성합니다
✅ setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app){
app.use(
createProxyMiddleware( '/api', {
target: 'https://openapi.naver.com',
changeOrigin: true,
// 하단 처리는 필수로 해주어야 한다. 아래의 내용이 없으면 url 경로에
// api가 추가되어 경로를 찾을 수 없어진다.
pathRewrite:{
'^/api/':'/'
}
})
)
};
보시면 아시겠지만 https://openapi.naver.com를 /api로 설정하여
/api를 proxy로 설정하여 호출하는 방식입니다
💡 axios 변경
❛ https://openapi.naver.com/v1/search/movie.json ❜ 👉👉 ❛ /api/v1/search/movie.json ❜ 변경하여 작성해주시면 됩니다.
const {data: {
items
}} = await axios.get('/api/v1/search/movie.json',{
params:{
query: search,
display: 20
},
headers: {
'X-Naver-Client-Id': ID_KEY,
'X-Naver-Client-Secret': SECRET_KEY
}
});
두 번째 방식의 장점은
여러 개의 proxy를 설정할 수 있다는 점입니다
👏👏👏
다만, 이 방법도 역시 서버에서는 불가능한 것으로 보입니다😅
제가 git pages나 별도의 서버에서 nginx에서 올려봤으나 되지 않았습니다.
😉 만약 되시는 분이 계시면 알려주세요 🙏
🎈 자 드디어 마무리입니다
이번 포스팅에서는
✅ 네이버 검색 API를 이용하여 영화 정보 가져오기
✅ CORS를 처리하는 방법 One
✅ CORS를 처리하는 방법 Two
를 알아보았습니다
전반적인 소스는 LottsGithub에서 참조하시면 됩니다 😁
다음 포스팅은 서버에서 올려도 CORS를 요리조리 피해
해결할 수 있는 방법에 대해 설명하도록 하겠습니다
(생각보다 쉬울 수 있어요)
👏👏👏👏👏👏
'Programming > ReactJS' 카테고리의 다른 글
React로 영화 검색 사이트를 만들어보자 - 세번째 네이버 API 사용 / React 배포 / CORS 설정 하기 (7) | 2020.04.03 |
---|---|
React로 영화 정보를 검색해보자 - 첫번째 React 설치 / 네이버 API 사용기 (7) | 2020.03.27 |