외부 API 연동해 뉴스 뷰어 만들기

Haechan Kim·2022년 1월 28일
0

React

목록 보기
12/13
post-thumbnail

https://github.com/bluesun147/news-viewer
비동기 작업에는 흔히 콜백함수가 쓰인다.
하지만 콜백함수가 여러번 중첩되어 가독성이 나빠지기 쉽고
이를 콜백 지옥 이라고 부른다.

  • Promise
    이럴 해결하기 위해 ES6부터 Promise 가 도입되었다.
<script>
    function inc(number, callback) {
        setTimeout(() => {
            const result = number + 10;
            if (callback) {
                callback(result);
            }
        }, 2000)
    }

    /*console.log('콜백 지옥');
    inc(3, result => {
        console.log(result);
        inc(4, result => {
            console.log(result);
            inc(5, result => {
                console.log(result);
                inc(6, result => {
                console.log(result);
            })
        })
    })
})*/

    function inc2(number) {
        const promise = new Promise((resolve, reject) => {
            // resolve는 성공, reject는 실패
            setTimeout(() => {
                const result = number + 10;
                if (result > 40) {
                    const e = new Error('too big');
                    return reject(e);
                }
                resolve(result); // 성공
            }, 2000);
        });
        return promise;
    }

    inc2(0)
    .then(number => { // Promise에서 resolve된 값은 .then 통해 받음
        console.log(number);
        return inc2(number);
    })
    .then(number => {
        console.log(number);
        return inc2(number);
    })
    .then(number => {
        console.log(number);
        return inc2(number);
    })
    .then(number => {
        console.log(number);
        return inc2(number);
    })
    .then(number => {
        console.log(number);
        return inc2(number);
    })
    .catch(e => { // 도중에 에러 발생시 .catch 통해 알수있다
        console.log(e);
    })

</script>
  • async/await
    async/await은 Promise를 더 쉽게 사용하게 해주는 syntax sugar이다.
    함수 앞부분에 async 붙이고 해당 함수 내부에서 Promise 앞부분에 await 키워드 사용한다.
function inc2(number) {
    const promise = new Promise((resolve, reject) => {
        // resolve는 성공, reject는 실패
        setTimeout(() => {
            const result = number + 10;
            if (result > 40) {
                const e = new Error('too big');
                return reject(e);
            }
            resolve(result); // 성공
        }, 2000);
    });
    return promise;
}

async function runTasks() {
    try {
        let result = await inc2(0);
        console.log(result);
        result = await inc2(result);
        console.log(result);
        result = await inc2(result);
    } catch (e) {
        console.log(e);
    }
  • axios
    axios는 js HTTP 클라이언트이다.
    이 라이브러리의 특징은 HTTP 요청을 Promise 기반으로 처리한다는 점.
import React, {useState} from 'react';
import axios from 'axios';
// App.js
const App = () => {
  const [data, setData] = useState(null);
  // axios.get 함수는 파라미터로 전달된 주소에 GET요청을 함.
  // 이에 대한 결과는 .then 통해 비동기적으로 확인 가능
  const onClick = () => {
    // 아래 주소에서 제공하는 가짜 API 호출하고 이에 대한 응답 컴포넌트 상태에 넣어서 보여줌
    axios.get('https://jsonplaceholder.typicode.com/todos/1').then(response => {
      setData(response.data);
    });
  };

  // async 적용시킨 onClick
  // 화살표 함수에 async/await 적용시에는 async () => {} 와 같은 형식으로 적용
  const onClick2 = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/todos/1'
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  }
  return (
    <div>
      <div>
        <button onClick={onClick2}>불러오기</button>
      </div>
      {data && <textarea rows = {7} value = {JSON.stringify(data, null, 2)} readOnly={true}/>}
    </div>
  );
};

export default App;

  • newsapi API 키 발급받기
    이 프로젝트에서는 newsapi에서 제공하는 API 사용해 최신 뉴스를 불러와 보여줄 것이다.
    먼저 newsapi에서 API 키를 발급 받아야 한다.
    발급받은 키를 추후 API 요청 시 API 주소의 쿼리 파라미터로 사용하면 된다.

https://newsapi.org/s/south-korea-news-api

사용할 API 주소는 전체 뉴스 불러오기, 특정 카테고리 뉴스 불러오기로 두가지 형태이다.
이전에 사용한 JSONPlaceholder 가짜 API를 전체 뉴스를 불러오는 API로 대체해 보자.

...
const onClick2 = async () => {
  try {
    const response = await axios.get(
      'https://newsapi.org/v2/top-headlines?country=kr&apiKey=b1018dcfde7246b2a3924270fb22d4b1'
    );
    setData(response.data);
  } catch (e) {
    console.log(e);
  }
}
...

이 데이터를 화면에 예쁘게 보여주면 됨.

  • 뉴스 뷰어 UI 만들기
    styled-components를 사용해 뉴스 정보를 보여줄 컴포넌트를 만들자.

  • NewsItem 만들기
    각 뉴스 정보를 보여주는 컴포넌트
    먼저 뉴스 데이터에 어떤 필드 있는지 확인해보자.

{
"source": {
  "id": null,
  "name": "Donga.com"
},
"author": null,
"title": ""새 집 냄새" "주택 청약 고마워!"…이시언 아파트 공개 - 동아일보",
"description": "배우 이시언(37)이 자신의 새 아파트를 공개했다.  이시언은 25일 방송한 MBC 예능 '나 혼자 산다'에서 정든 옛집을 떠나 새 아파트로 이사했다.  이사한 아파트에 도착한 …",
"url": "http://news.donga.com/Main/3/all/20190126/93869524/2",
"urlToImage": "http://dimg.donga.com/a/600/0/90/5/wps/NEWS/IMAGE/2019/01/26/93869523.2.jpg",
"publishedAt": "2019-01-26T00:21:00Z",
"content": null
}

위 코드는 각 뉴스 데이터가 지닌 정보로 이뤄진 JSON 객체이다.
그중 다음 필드를 리액트 컴포넌트에 나타낼 예정.
title: 제목
description: 내용
url: 링크
urlToImage: 뉴스 이미지

NewsItem 컴포넌트는 article이라는 객체를 props로 통째로 받아와 사용

import React from "react";
import styled from "styled-components";
// NewsItem.js
// 각 뉴스 정보를 보여주는 컴포넌트
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;
  }
`;
// article 객체를 props로 통째로 받아와 사용
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 만들기
import React from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
// NewsList.js
// API 요청하고 뉴스 데이터가 들어있는 배열을 
// 컴포넌트 배열로 변환해 렌더링해주는 컴포넌트
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
  }`

// 나중에 이 컴포넌트에서 API 요청하게 됨
// 아직 데이터 불러오고 있지 않으니 예시 데이터 넣고 보이게.
const sapmleArticle = {
    title: 'title',
    description: 'des',
    url: 'https://google.com',
    urlToImage: 'https://via.placeholder.com/160'
};

const NewsList = () => {
    return (
        <NewsListBlock>
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
            <NewsItem article = {sapmleArticle} />
        </NewsListBlock>
    );
};

export default NewsList;
import React from "react";
import NewsList from "./NewsList";
// App.js
const App = () => {
  return <NewsList/>
};

export default App;

  • 데이터 연동하기
    이제 NewsList 컴포넌트에서 이전에 연습했던 뉴스 API를 호출해보자.
    컴포넌트가 화면에 보이는 시점에 API를 요청.
    이때 useEffect를 사용해 컴포넌트가 처음 렌더링 되는 시점에 API 요청하면 됨.
    주의할 점은 useEffect에 등록하는 함수에는 async 붙이면 안된다는 것이다.
    useEffect에서 반환해야 하는 값은 뒷정리(cleanup) 함수이기 때문.
    ㄴ async/await은 Promise객체 반환하기 때문
    따라서 async/await 사용하고 싶다면 함수 내부에 async 키워드가 붙은 또 다른 함수를 만들어서 사용해야 한다.

laoding이라는 상태로 관리하여 API 요청이 대기 중인지도 판별할 것.
요청 대기중일때는 loading 값이 true, 요청 끝나면 false

import React, {useState, useEffect} from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
import axios from 'axios';

// NewsList.js
// API 요청하고 뉴스 데이터가 들어있는 배열을 
// 컴포넌트 배열로 변환해 렌더링해주는 컴포넌트
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
  }`

// 나중에 이 컴포넌트에서 API 요청하게 됨
// 아직 데이터 불러오고 있지 않으니 예시 데이터 넣고 보이게.
const sapmleArticle = {
    title: 'title',
    description: 'des',
    url: 'https://google.com',
    urlToImage: 'https://via.placeholder.com/160'
};

const NewsList = () => {
    const [articles, setArticles] = useState(null);
    // API 요청 대기중일때 true, 끝나면 false
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        // async 사용하는 함수 따로 선언
        const fetchData = async() => {
            setLoading(true); // 요청 대기중
            try{
                const response = await axios.get(
                    'https://newsapi.org/v2/top-headlines?country=us&apiKey=b1018dcfde7246b2a3924270fb22d4b1'
                );
                setArticles(response.data.articles); // 위 주소에서 받아온 데이터에서 articles 꺼냄
            } catch (e) {
                console.log(e);
            }
            setLoading(false); // 요청 끝남
        };
        fetchData();
    }, []);

    // 대기 중일 때
    if (loading) {
        return <NewsListBlock>대기 중...</NewsListBlock>
    }
    // 아직 articles 값 설정안됐을 때
    if (!articles) { // 현재값 null 아닌지 검사 필수
        return null;
    }
    // articles 값 유효할 때
    return (
        <NewsListBlock>
            {// 데이터를 불러와서 뉴스 데이터 배열을 map함수 사용해 컴포넌트 배열로 변환
            // 주의할 점은 map 사용하기 전에 꼭 현재 값이 null이 아닌지 검사해야 함
            // 데이터 없을 때 null에는 map함수 없기때문에 렌더링 과정에서 오류 발생함
                articles.map(article => (
                    <NewsItem key = {article.url} article = {article}/>
                ))
            }
        </NewsListBlock>
    );
};

export default NewsList;

import React from "react";
import styled from "styled-components";
// Categories.js
// categories 배열 안에 name과 text값 들어있는
// 객체 넣어 한글로 된 카테고리와 실제 카테고리 값 연결시킴
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;
margin-top: 2rem;
@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;
  }

& + & {
    margin-left: 1rem;
  }
`;

const Categories = () => {
    return (
        <CategoriesBlock>
            
            {categories.map(c => ( // categories배열을 Category컴포넌트 배열로 변환
                <Category key = {c.name}>{c.text}</Category>
        ))}
        </CategoriesBlock>
    );
};

export default Categories;
import React from "react";
import Categories from "./Categories";
import NewsList from "./NewsList";
// App.js
const App = () => {
  return (
    <>
    <Categories />
    <NewsList />
    </>
  );
};

export default App;

이제 App에서 category 상태를 useState로 관리해보자.
추가로 category 값 업데이트하는 onSelect 함수도 만들기.
그 후 category와 onSelect 함수를 Category 컴포넌트에게 props로 전달.
category값을 NewsList 컴포넌트에게도 전달해줘야 함.

import React, {useState, useCallback} from "react";
import Categories from "./Categories";
import NewsList from "./NewsList";
// App.js
const App = () => {
  const [category, setCategory] = useState('all');
  // category값을 업데이트하는 함수
  const onSelect = useCallback(category => setCategory(category), []);
  return (
    <>
    {/* 만든 후 Categories, NewsList 컴포넌트에 props 로 전달. */}
    <Categories category = {category} onSelect={onSelect}/>
    <NewsList category = {category}/>
    </>
  );
};

export default App;

Categories에서는 props로 전달받은 onSelect를 각 Category컴포넌트의 onClick으로 설정해 주고 현재 선택된 카테고리 값에 따라 다른 스타일을 적용시켜보자.

import React from "react";
import styled, {css} from "styled-components";
// Categories.js
// categories 배열 안에 name과 text값 들어있는
// 객체 넣어 한글로 된 카테고리와 실제 카테고리 값 연결시킴
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;
margin-top: 2rem;
@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: #3bc9db;
      }
  `}

& + & {
    margin-left: 1rem;
  }
`;

const Categories = ({onSelect, category}) => { // 파라미터에 props
    return (
        <CategoriesBlock>
            
            {categories.map(c => ( // categories배열을 Category컴포넌트 배열로 변환
                <Category key = {c.name} 
                active = {category === c.name} 
                onClick={() => onSelect(c.name)}>{c.text}</Category>
        ))}
        </CategoriesBlock>
    );
};

export default Categories;

선택된 카레고리의 색이 변한것을 확인할 수 있다.

  • API 호출시 카테고리 지정하기
    지금은 뉴스 API 요청시 따로 카테고리 선택하지 않고 뉴스 목록 불러오고 있다.
    NewsList 컴포넌트에서 현재 props로 받아온 category에 따라 카테고리 지정해 API 요청하도록 구현해보자.
// NewsList.js
...
useEffect(() => {
    // async 사용하는 함수 따로 선언
    const fetchData = async() => {
        setLoading(true); // 요청 대기중
        try{
            // 현재 category값 무엇인지에 따라 요청할 주소가 동적으로 바뀌고 있다
            // category값이 all 이면 query값은 공백, 아니면 '&categpry=카테고리' 형태의 문자열 만들도록 함
            // 그리고 이 query 요청할 때 주소에 포함시켜줌
            const query = category === 'all' ? '' : `&category=${category}`;
            const response = await axios.get(
                `https://newsapi.org/v2/top-headlines?country=us${query}&apiKey=b1018dcfde7246b2a3924270fb22d4b1`
            );
            setArticles(response.data.articles); // 위 주소에서 받아온 데이터에서 articles 꺼냄
        } catch (e) {
            console.log(e);
        }
        setLoading(false); // 요청 끝남
    };
    fetchData();
}, [category]); // category값 바뀔때마다 뉴스 새로 불러와야 함.
...



카테고리에 따른 뉴스가 잘 나오는걸 확인할 수 있다.

  • 리액트 라우터 적용하기
    뉴스 뷰어에 리액트 라우터를 적용해보자.
    기존에는 카테고리 값을 useState로 관리했었는데
    이번에는 이 값을 리액트 라우터의 URL 파라미터 사용해 관리해보자.

  • 리액트 라우터의 설치 및 적용
    현재 프로젝트에 리액트 라우터 설치하고
    yarn add react-router-dom

index.js에서 리액트 라우터 적용

...
import { BrowserRouter } from ‘react-router-dom‘;
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
...

-NewsPage 생성
이번 프로젝트에서 라우터 적용할 때 만들어야 할 페이지는 단 하나이다.

import React from "react";
import Categories from "../Categories";
import NewsList from "../NewsList";
// NewsPages.js
const NewsPage = ({match}) => {
    // 카테고리가 선택안됐으면 기본값 all로 사용
    const category = match.params.category || 'all';
    return (
        <>
        <Categories />
        <NewsList category = {category} />
        </>
    );
};

export default NewsPage;

현재 선택된 category값을 URL 파라미터를 통해 사용할 것이므로 Categories 컴포넌트에서 현재 선택된 값 알려줄 필요도 없고, onSelect 함수 따로 전달할 필요도 없음.

import React from "react";
import { Route } from "react-router-dom";
import NewsPage from "./pages/NewsPages";
// App.js
const App = () => {
  // 물음표는 category값이 선택적(optional) 이라는 뜻
  // 즉 있을수도 있고 없을수도 있다는 뜻
  // category URL 파라미터가 없다면 전체 카테고리 택한것으로 간주
  return <Route path = '/:category?' component = {NewsPage} />;
};

export default App;
  • Categories에서 NavLink 사용하기
    Categories에서 기존의 onSelect함수 호출해 카테고리 선택하고, 선태된 카테고리에 다른 스타일 주는 기능을 NavLink로 대체해보자.
    NavLink는 Link컴포넌트와 비슷하지만 기능이 조금 더 부과된 것.

div, a, button, input처럼 일반 html 요소가 아닌 특정 컴포넌트에 styled-components 사용시 styled(컴포넌트이름)`` 과 같은 식으로 사용한다.

import React from "react";
import styled from "styled-components";
import { NavLink } from "react-router-dom";
// Categories.js
// categories 배열 안에 name과 text값 들어있는
// 객체 넣어 한글로 된 카테고리와 실제 카테고리 값 연결시킴
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;
margin-top: 2rem;
@media screen and (max-width:768px) {
width:100%;
overflow-x:auto;
}`

//const Category = styled.div`
const Category = styled(NavLink)`
  font-size: 1.125rem;
  cursor: pointer;
  white-space: pre;
  text-decoration: none;
  color: inherit;
  padding-bottom: 0.25rem;
  &:hover {
    color: #495057;
  }

  
&.props.active {
    font-weight: 600;
    border-bottom: 2px solid #22b8cf;
    color: #22b8cf;
    &:hover {
    color: #3bc9db;
    }
}

& + & {
    margin-left: 1rem;
  }
`;

const Categories = ({onSelect, category}) => { // 파라미터에 props
    return (
        <CategoriesBlock>
            
            {categories.map(c => ( // categories배열을 Category컴포넌트 배열로 변환
                <Category key = {c.name} 
                activeclassName = 'active'
                exact = {c.name === 'all'}
                to = {c.name === 'all' ? '/' : `/${c.name}`}
                >{c.text}</Category>
        ))}
        </CategoriesBlock>
    );
};

export default Categories;

NavLink로 만들어진 Category 컴포넌트에 to 값은 '/카테고리이름'으로 설정.
카테고리 중 전테보기의 경우 '/all' 대신 '/'로 설정.
to 값이 '/'가리킬때는 exact값을 true로 해줘야 함.
그렇지 않으면 다른 카테고리 선택됐을때도 전체보기 링크에 active스타일 적용됨.

  • usePromise 커스텀 Hook 만들기.
    컴포넌트에서 API 호출처럼 Promie 사용해야 하는 경우 코드 간결하게 작성 도와주는 커스텀 Hook 만들고 적용시켜 보자.
    만들 커스텀 Hook의 이름은 usePromise.
import {useState, useEffect} from 'react';
// usePromise.js
export default function usePromise(promiseCreator, deps) {
    // 대기중 / 완료 / 실패에 대한 상태 관리
    // usePromise의 의존 배열 deps를 파라미터로 받아옴
    // 받아온 deps 배열은 useEffect의 의존배열로 설정
    // 의존배열은 useEffect 두번째 파라미터. []
    const [loading, setLoading] = useState(false);
    const [resolved, setResolved] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => { // 렌더링될때마다 실행
        const process = async() => {
            setLoading(true); // loading을 true로
            try {
                const resolved = await promiseCreator();
                setResolved(resolved);
            } catch (e) {
                setError(e);
            }
            setLoading(false); // 끝나면 loading값 false로
        };
        process();
    }, deps);

    return [loading, resolved, error];
}

프로젝트의 다양한 곳에 사용될 수 있는 유틸 함수들은 보통 src디렉터리에 lib디렉터리 만든 후 그 안에 작성.

NewsList 컴포넌트에서 usePromose 사용해보자.

import React from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
import axios from 'axios';
import usePromise from "./lib/usePromise";
// NewsList.js
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=us${query}&apiKey=b1018dcfde7246b2a3924270fb22d4b1`);
    }, [category]);

    // 대기 중일 때
    if (loading) {
        return <NewsListBlock>대기 중...</NewsListBlock>;
    }

    // 아직 response 값이 설정되지 않았을 때
    if (!response) {
        return null;
    }

    // 에러 발생 시
    if (error) {
        return <NewsListBlock>에러 발생!!</NewsListBlock>
    }

    // response 값이 유효할 때
    const {articles} = response.data;
    return (
        <NewsListBlock>
            {articles.map(article => (
                <NewsItem key = {articles.url} article = {article} />
            ))}
        </NewsListBlock>
    );
};

export default NewsList;

usePromise 사용하면 NewsList에서 대기중 상태 관리와 useEffect 설정 직접 안해도 되므로 코드 간결해짐.
요청 상태 관리할 때 커스텀 Hook 사용하면 좋다.

  • 정리
    외부 API 연동해 사용하는 방법과 지금까지 배운것 활용해 프로젝트를 개발했다.
    리액트 컴포넌트에서 API 연동해 개발 시 절대 잊으면 안되는 것은 useEffect에 등록하는 함수는 async로 작성하면 안된다는 점.
    대신 함수 내부에 async 함수 따로 만들어줘야 함.

netlify 이용해 사이트 배포해보려 했지만 newsAPI.org는 localhost아닌 다른 곳에서의 키 요청을 거부한다고 한다.
https://agitated-thompson-9e22f1.netlify.app
이 링크를 통해선 오류가 나지만 localhost에서는 오류없이 실행됨.

https://github.com/bluesun147/news-viewer

0개의 댓글