목차
1) axios.get
const [data, setData] = useState(null);
const onClick = async () => {
axios
.get('https://jsonplaceholder.typicode.com/todos/1')
.then((response) => {
setData(response.data);
});
};
2) async - await
const [data, setData] = useState(null);
const onClick = async () => {
try {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/todos/1'
);
setData(response.data);
} catch (e) {
console.log(e);
}
};
styled-components를 이용해 컴포넌트를 만들었다.
import styled from 'styled-components'
가 선행돼야 한다.
rel="noopener noreferrer"
는 '새창으로 열기'의 보안상 취약점을 보완하기 위한 방법이다.
// components/NewsItem.js
const NewsItemBlock = styled.div`
display: flex;
.thumbnail {
margin-right: 1rem;
img {
display: block;
width: 160px;
height: 100px;
object-fit: cover;
}
}
.contentes {
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>
);
};
// components/NewsList.js
import React from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
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 sampleArticle = {
title: '제목',
description: '내용',
url: 'https://google.com',
urlToImage: 'https://via.placeholder.com/160',
};
const NewsList = () => {
return (
<NewsListBlock>
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
</NewsListBlock>
);
};
export default NewsList;
// App.js
import React, { useState } from 'react';
import NewsList from './components/NewsLIst';
const App = () => {
return <NewsList />;
};
export default App;
useEffect는 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.(클래스형 컴포넌트의 componentDidMount + componentDidUpdate). 두번째 파라미터로 빈 배열([]
)이 보인다. 이는 처음 렌더링될 때만 실행하고, 업데이트될 때는 실행하지 않도록 해준다. newsapi에서 한 번만 데이터를 불러오기 위함이다.
// NewsList.js
const NewsList = () => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
'https://newsapi.org/v2/top-headlines?country=kr&apiKey=bd27922d5692413bad59ef0ca957c20b'
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <NewsListBlock>대기 중 ...</NewsListBlock>;
}
if (!articles) {
return null;
}
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
// App.js
import React, { useState, useCallback } from 'react';
import NewsList from './components/NewsLIst';
import Categories from './components/Categories';
const App = () => {
const [category, setCategory] = useState('all');
const onSelect = useCallback((category) => setCategory(category), []);
return (
<>
<Categories category={category} onSelect={onSelect} />
<NewsList category={category} />
</>
);
};
export default App;
카테고리 기능을 추가하기 위해 category state 및 이를 변경해줄 onSelect를 추가했다. Categories, NewsList 컴포넌트에 props로 보내준다.
const onSelect = useCallback((category) => setCategory(category), [])
useCallback과, 두 번째 파라미터로 빈 배열을 준 이유는 다음과 같다.
useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다. setCategory(cateogry)를 매번 새롭게 만들지 않고, 처음에 만들어 놓은 것을 쓰겠다는 것이다.
useCallback의 두번째 파라미터인 배열의 내용이 바뀔 때까지 저장해놓고 재사용하겠다는 것이다. 만약 []
이라면 렌더링될 때 만들었던 함수를 계속해서 쓰겠다는 것이다. 어떤 조건과도 상관없이 재활용한다.
// 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: 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 (
<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;
// NewsList.js
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
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 [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const query = category === 'all' ? '' : `&category=${category}`;
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=bd27922d5692413bad59ef0ca957c20b`
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
if (loading) {
return <NewsListBlock>대기 중 ...</NewsListBlock>;
}
if (!articles) {
return null;
}
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
useEffect는 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.(클래스형 컴포넌트의 componentDidMount + componentDidUpdate) '3. 데이터 연동하기'에서는 useEffect의 두 번째 파라미터로 빈 배열을 받았다. 이 때는 카테고리가 없었고, 모든 종류의 기사에 대해 전부 '한 번만' 받아오면 됐기 때문이다.
이제는 category가 변경될 때마다 데이터를 불러오도록 바꿔줘야 한다. 그 카테고리에 맞는 기사를 노출해야 하기 때문이다.
//categories
import React from 'react';
import styled, { css } from 'styled-components';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비즈니스',
},
{
name: 'entertainment',
text: '엔터테인먼트',
},
{
name: 'health',
text: '건강',
},
{
name: 'scinece',
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.div`
font-size: 1.125rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
}
${(props) =>
props.active &&
css`
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&: hover {
color: #3bc9dbwidth;
}
`}
& + & {
margin-left: 1rem;
}
`;
const Categories = ({ onSelect, category }) => {
return (
<CategoriesBlock>
{categories.map((c) => (
<Category
key={c.name}
active={category === c.name}
onClick={() => onSelect(c.name)}
>
{c.text}
</Category>
))}
</CategoriesBlock>
);
};
export default Categories;
클릭한 카테고리에 따라서 Nav의 색상이 변하는 기능을 추가했다. active
라는 props 때문에 가능하고, Category에서 active
를 받아 그에 맞게 css가 달라지도록 설정했다.