2021-04-08(목)
뉴스 리스트를 화면에 보여주기 위해 우리는 뉴스 API를 사용할 것인데, Axios를 통해서 서버에 요청할 것이다. 이전에 작업한 라우터를 통해서 뉴스의 카테고리도 나눌 예정이라, 라우터 모듈도 추가해주자!
yarn add axios
or npm i axios
yarn add react-router-dom
or npm i react-router-dom
그리고 스타일 모듈도 설치 yarn add styled-components
https://newsapi.org/s/south-korea-news-api 링크에 있는 API를 사용하여 예제를 완성할 예정이다.
뉴스 컴포넌트
- 뉴스 요소의
NewsItem 컴포넌트- 뉴스 요소들을 나열하는
NewsList 컴포넌트- 뉴스의 카테고리 메뉴 설정을 위한
Categorys 컴포넌트페이지 컴포넌트
- 뉴스의 카테고리 메뉴와 뉴스 요소 목록을 출력하는 페이지
NewsPages 컴포넌트라이브러리
- 서버 통신의 비동기적 처리에 대한 편리함을 제공해주는
usePromise 함수루트
- APP.js에서 라우트 설정
- Index.js에서 라우터 사용 설정
// components/NewsItem.js
import React from 'react';
import styled from 'styled-components';
const NewsItemBlock = styled.div`
display: flex;
.thumbnail {
margin-right: 1rem;
img {
display: block;
width: 150px;
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 (
<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} target="_blank" rel="noopener noreferrer">
{title}
</a>
</h2>
<p>{description}</p>
</div>
</NewsItemBlock>
);
};
export default NewsItem;
API_KEY에 해당하는 부분은 뉴스API 가입을 통해서 API_KEY를 얻어 기입하면 된다
// components/NewsList.js
import React, { useEffect, useState } 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 NewsList = ({ category }) => {
const [loading, response, error] = usePromise(() => {
const query = category === 'all' ? '' : `&category=${category}`;
return axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=API_KEY`
);
}, [category]);
if (loading) {
return <NewsListBlock>대기중.....</NewsListBlock>;
}
if (!response) {
return null;
}
//response값이 유효할때
const { articles } = response.data;
return (
//article값이 유효하면
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
// components/Categories.js
import React from 'react';
import styled, { css } from 'styled-components';
import { NavLink } from 'react-router-dom';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비즈니스',
},
{
name: 'entertainment',
text: '엔터테인먼트',
},
{
name: 'health',
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;
}
`;
//styled(컴포넌트)``
const Category = styled(NavLink)`
font-size: 1.2rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.3rem;
&:hover {
color: #495057;
}
&.active {
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
}
& + & {
margin-left: 1rem;
}
`;
const Categories = () => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
key={c.name}
//active={category === c.name}
// onClick={() => onSelect(c.name)}
activeClassName="active"
exact={c.name === 'all'}
//NavLink로 만들어진 Category컴포넌트에 to값
//"/카테고리이름"
// /all-> /
to={c.name === 'all' ? '/' : `/${c.name}`}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
export default Categories;
// lib/usePromise.js
import { useState, useEffect } from 'react';
export default function usePromise(promiseCreator, deps) {
const [loading, setLoding] = useState(false); //대기중
const [resolved, setResolved] = useState(null); //완료
const [error, setError] = useState(null); //실패
useEffect(() => {
const process = async () => {
setLoding(true);
try {
const resolved = await promiseCreator();
setResolved(resolved);
} catch (error) {
setError(error);
}
setLoding(false);
};
process();
}, deps);
return [loading, resolved, error];
}
// pages/NewsPages.js
import React from 'react';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
const NewsPage = ({ match }) => {
const category = match.params.category || 'all';
return (
<>
{/* 현재 선택된 카테고리 값을 URL파라미터를 통해 사용하므로
Categories컴포넌트에서 현재 선택된 카테고리값을 알려줄 필요없다.
*/}
<Categories />
<NewsList category={category} />
</>
);
};
export default NewsPage;
// App.js
import { Route } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
export const App = () => {
return <Route path="/:category?" component={NewsPage} />;
};
export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();
좋아요~~!!