안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
외부 API를 연동하여 뉴스 뷰어 만들기
newsapi API 키 발급받기
API 키 발급을 위해 https://newsapi.org/register 해당 주소에 접속 → 가입
발급받은 API 키는 추후 API 요청 시 API 주소의 쿼리 파라미터로 넣어 사용합니다.
아래 링크에 접속하면 한국 뉴스를 가져오는 API에 대한 설명서가 있습니다.
https://newsapi.org/s/south-korea-news-api
여러 카테고리가 존재하는 것 또한 확인할 수 있고, API를 가져와 사용해보겠습니다.
import logo from './logo.svg';
import './App.css';
import { useState } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState(null);
const onClick = async () => {
try {
const response = await axios.get("https://newsapi.org/v2/top-headlines?country=kr&apiKey=460a690efc5748c7979f8762eea705fe");
setData(response.data);
} catch(e) {
console.log(e);
}
}
return (
<div>
<div>
<button onClick={onClick}>불러오기</button>
</div>
{data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly={true} />}
</div>
);
}
export default App;
apiKey에 발급받은 Key를 넣으면 되는데, 가입 후 접속하면 자동으로 입력 되어 있습니다.
뉴스가 잘 불러와지는 것을 확인할 수 있습니다.
뉴스 뷰어 UI 만들기
styled-components를 이용할 것이므로 설치해주겠습니다.
$ yarn add styled-components
src 디렉터리에 컴포넌트들을 모을 component 디렉터리를 생성해주고 그 안에 아래 컴포넌트들을 만들 예정입니다.
NewsItem 컴포넌트 → 각 뉴스 정보를 보여주는 컴포넌트
NewsList 컴포넌트 → API 요청하고 뉴스 데이터가 들어 있는 배열을 컴포넌트 배열로 반환하여 렌더링
NewsItem 만들기
컴포넌트 생성 전 어떤 데이터들이 들어가 있는 지 확인해보겠습니다.
"articles": [
{
"source": {
"id": null,
"name": "Khan.co.kr"
},
"author": "베이징 | 이종섭 특파원",
"title": "감염자 폭증에 결국 또 봉쇄…베이징서 재현된 '패닉 바잉' - 경향신문",
"description": "지난 23일 오후 중국 수도 베이징의 한인 밀집 거주지역인 차오양(朝陽)구 왕징(...",
"url": "https://m.khan.co.kr/article/202211241336001",
"urlToImage": "https://img.khan.co.kr/news/2022/11/24/news-p.v1.20221124.26d7791d59a44bd9be1f5885d0797e43_P1.jpg",
"publishedAt": "2022-11-24T04:36:00Z",
"content": "23 . \r\n23 (朝陽) (望京) . . 4 19 () .\r\n . 20 3 . 3 (PCR) . . .\r\n · . . 24 0 1611( ) 19 . 10 100 2 16 . 200 . 606 .\r\n19 . \r\n. (遼寧) (瀋陽) 5 9 . (河南) (鄭州) 25 5 8 . . (廣東) (廣州) (海珠) . 11 .\r\n . (SNS) . (微博) . … [+33 chars]"
},
각 데이터가 지니고 있는 정보로 이루어진 JSON 객체임을 알 수 있으며, 아래 필드들을 나타내보겠습니다.
- title : 제목
- description : 내용
- url :링크
- urlToImage : 뉴스 이미지
NewsItem.js
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; // img나 video 같은 요소의 콘텐츠 크기를 어떤 방식으로 조절할 것인지 설정
}
}
.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;
article 객체를 props로 통째로 받아 사용합니다.
※ HTML a태그의 rel?
http://www.tcpschool.com/html-tag-attrs/a-rel
NewsList 만들기
우선 예시로 데이터를 넣어두었으며, 추후 데이터를 받아와 렌더링해줄 예정입니다.
NewsList.js
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} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
</NewsListBlock>
)
}
export default NewsList;
App.js
import NewsList from './components/NewsList';
function App() {
return <NewsList />
}
export default App;
데이터 연동하기
컴포넌트가 화면에 보이는 시점에 API를 요청하기 위해 useEffect을 사용하여 컴포넌트가 처음 렌더링 되는 시점에 API를 요청해보겠습니다.
☆ useEffect에 등록하는 함수에 async를 붙이면 안됨 → useEffect에서 반환해야 하는 값은 뒷정리 함수이기 때문!
따라서 useEffect 내부에서 async/await을 사용하고 싶다면, 함수 내부에 async 키워드가 붙은 다른 함수를 생성하여 사용해줍니다.
NewsList.js
import { 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 = () => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false); // API요청이 대기 중인지 판별하기 위한 loading 상태 선언
useEffect(() => {
// async를 사용하는 함수 별도 선언
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get("https://newsapi.org/v2/top-headlines?country=kr&apiKey=460a690efc5748c7979f8762eea705fe");
setArticles(response.data.articles);
} catch(e) {
console.log(e);
}
setLoading(false);
};
fetchData();
console.log(articles);
}, []); // 처음 렌더링될 때만 실행
// 대기 중일 때
if(loading) {
return <NewsListBlock>대기 중...</NewsListBlock>
}
// 아직 articles 값이 설정되지 않았을 때
if(!articles) {
return null;
}
// article값이 유효하고 데이터를 가져온 후 (loading = false)
return (
<NewsListBlock>
{articles.map(article => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
)
}
export default NewsList;
잘 불러와지는 것을 확인할 수 있습니다. (신기..)
이 때 map 함수를 이용하여 각 article를 불러왔는데, 반드시 !articles를 조회하여 null 여부를 검사해야합니다.
그렇지 않으면 데이터가 없을 때는 null이 들어있을 텐데 map 함수를 사용한다면 오류가 발생할 것이기 때문입니다.