본문 바로가기
JavaScript/React

React) 외부 API를 연동하여 뉴스 뷰어 만들기 ① - API 키 발급, 데이터 연동

by 박채니 2022. 11. 24.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

외부 API를 연동하여 뉴스 뷰어 만들기

 

newsapi API 키 발급받기

 

API 키 발급을 위해 https://newsapi.org/register 해당 주소에 접속 → 가입

newsapi 가입 완료

 

발급받은 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일 오후 중국 수도 베이징의 한인 밀집 거주지역인 차오양(朝陽)구 왕징(&#2...",
      "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

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

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 함수를 사용한다면 오류가 발생할 것이기 때문입니다.