예제는 길벗의 리액트를 다루는 기술을 보면서 공부한 예제입니다.
화면 결과
axios로 뉴스 api 호출 준비(ft. promise)
아래의 사이트에 회원 가입 후에 받은 각자의 API 키를 이용하여 데이터에 접근할 수 있습니다.
발급받은 API 키를 가지고 나중에 API를 요청할 때 API 주소의 쿼리 파라미터를 넣어서 사용합니다.
폴더 구조
뉴스 사이트 디자인
디자인은 styled-components 라이브러리를 사용합니다.
yarn add styled-components
react router 설치
npm i react-router-dom
//또는
yarn add react-router-dom
index.js - router 적용
react-router-dom 적용
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
pages > NewPage.js
react router의 url 파라미터를 사용하여 관리
url 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서 받아 오는 match라는 객체 안의 params 값을 참조합니다.
match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있습니다.
import React from 'react';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
const NewsPage = ({ match }) => {
//카테고리가 선택되지 않았으면 기본값을 all 사용
const category = match.params.category || 'all';
return (
<div>
<Categories />
<NewsList category={category} />
</div>
);
};
export default NewsPage;
App.js
route 정의
import React from 'react';
import { Route } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
const App = () => {
return (
<div>
<Route path='/:category?' component={NewsPage} />;
</div>
);
};
export default App;
components > Categories.js - 카테고리 선택 UI 세팅, NavLink 사용하기
NavLink로 만들어진 Category 컴포넌트에 to 값은 '/카테고리 이름'으로 설정해 주었습니다.
import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비즈니스',
},
{
name: 'entertainment',
text: '엔터테인먼트',
},
{
name: 'health',
text: '건강',
},
{
name: 'science',
text: '과학',
},
{
name: 'sports',
text: '스포츠',
},
{
name: 'technology',
text: '기술',
},
];
const CategoriesBlock = styled.div`
display: flex;
padding: 1rem;
width: 768px;
margin: 0 auto;
@media screen and (max-width: 768px) {
width: 100%;
overflow-x: auto;
}
`;
const Category = styled(NavLink)`//NavLink
font-size: 1.125rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
}
&.active{
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
}
& + & {
margin-left: 1rem;
}
`;
const Categories = () => {
return (
<div>
<CategoriesBlock>
//NavLink
{categories.map(item => (
<Category key={item.name}
activeClassName='active'
exact={item.name === 'all'}
to={item.name === 'all' ? '/' : `${item.name}`}
>{item.text}</Category>
))}
</CategoriesBlock>
</div>
);
};
export default Categories;
components > NewsList.js - API 호출
props로 받아온 category에 따라 각 category별 API 호출
주의할 점: map 함수를 사용하여 컴포넌트 배열로 변환할 때, map 함수를 사용하기 전에 꼭 !articles를 조회하여 해당 값이 현재 null이 아닌거 검사해야 합니다.
이 작업을 하지 않으면, 아직 데이터가 없을 경우 null에는 map 함수가 없기 때문에 렌더링 과정에가 오류가 발생합니다.
useEffect에서 usePromise 커스텀 hook으로 리팩토링
import React, { useState, useEffect } from 'react';
import NewsItem from './NewsItem';
import styled from 'styled-components';
import axios from 'axios';
import usePromise from '../lib/usePromise';
const NewsListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 0 auto;
margin-top: 2rem;
@media screen and (max-width: 768px) {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
`;
// const sample = {
// title: 'title',
// desc: 'story',
// url: 'https://google.com',
// urlToImg: 'https://via.placeholder.com/160',
// }
const NewsList = ({ category }) => {
const [loading, response, error] = usePromise(() => {
const query = category === 'all' ? '' : `&category=${category}`;
const url = `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=zzz`;
return axios.get(url);
}, [category]);
//const [articles, setArticles] = useState(null);
//const [loading, setLoading] = useState(false);
// useEffect(() => {
// setLoading(true);
// const query = category === 'all' ? '' : `&category=${category}`;
// const url = `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=xxxx`;
// axios.get(url)
// .then(res => {
// console.log(res.data.articles);
// setArticles(res.data.articles)
// })
// .catch(err => console.log(err))
// setLoading(false);
// }, [category]);
if (loading) {
return <NewsListBlock>Loading...</NewsListBlock>
}
if (!response) { //아직 articles 값이 설정되지 않았을때,
return null;
}
if (error) {
return <NewsListBlock>error!</NewsListBlock>
}
const { articles } = response.data;
return (
<div>
{/* <NewsListBlock>
<NewsItem article={sample} />
<NewsItem article={sample} />
<NewsItem article={sample} />
</NewsListBlock> */}
{articles ? <NewsListBlock>
{articles.map(item => <NewsItem article={item} />)}
</NewsListBlock> : null}
</div>
);
};
export default NewsList;
components > NewsItem.js - 최하위 컴포넌트, 화면 디자인 기능
import React from 'react';
import styled from 'styled-components';
const NewsItemBlock = styled.div`
display: flex;
margin-top: 3rem;
.thumbnail {
margin-right: 1rem;
img {
display: block;
width: 160px;
height: 100px;
object-fit: cover;
}
}
.contents {
h2 {
margin: 0;
a {
color: black;
}
}
p {
margin: 0;
line-height: 1.5;
margin-top: 0.5rem;
white-space: normal;
}
}
& + & {
margin-top: 3rem;
}
`;
const NewsItem = ({ article }) => {
const { title, description, url, urlToImage } = article;
return (
<div>
<NewsItemBlock>
{urlToImage && (
<div className='thumbnail'>
<a href={url} target='_blank' rel='noopener noreferrer'>
<img src={urlToImage} alt='thumbnail' />
</a>
</div>
)
}
<div className='contents'>
<h2>
<a href={url} arget='_blank' rel='noopener noreferrer'>
{title}
</a>
</h2>
<p>{description}</p>
</div>
</NewsItemBlock>
</div>
);
};
export default NewsItem;
lib > usePromise.js - 대기 중/완료/실패에 대한 상태 관리
promise의 대기 중, 완료, 실패 결과에 대한 상태를 관리하며, usePromise의 의존 배열 deps를 파라미터로 받아 옵니다.
파라미터로 받아 온 deps 배열은 usePromise 내부에서 사용한 useEffect의 의존 배열로 설정됩니다.
usePromise를 사용하며 NewsList에서 대기 중 상태 관리와 useEffect 설정을 직접 하지 않아도 되므로 코드가 간결해 집니다.
import { useState, useEffect } from 'react';
export default function usePromise(promiseCreator, deps) {
const [loading, setLoading] = useState(false);
const [resolved, setResolved] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const process = async () => {
setLoading(true);
try {
const resolved = await promiseCreator();
setResolved(resolved);
} catch (e) {
setError(e);
}
setLoading(false);
};
process();
}, deps);
return [loading, resolved, error];
}
'개발 > React' 카테고리의 다른 글
[react] react 상태관리 context API, useContext 사용법 (0) | 2021.04.26 |
---|---|
[react] react-router-dom 설치, 라우팅하기 (0) | 2021.04.20 |
[react] react로 axios로 API 호출 (ft. promise, hooks) (2) | 2021.04.19 |
[react] react로 지뢰찾기 게임 만들기 ver.2 (0) | 2021.04.16 |
[react] react로 지뢰찾기 게임 만들기 ver.1(ft. context API, useReducer) (0) | 2021.04.14 |
댓글