-
외부 API를 연동해 뉴스 뷰어 만들기Javascript/React.js 2020. 5. 12. 20:21
axios
현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트이다. Promise 기반으로 처리한다.
예시
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport React, { useState } from 'react'; import axios from 'axios'; const App = () => { const [data, setData] = useState(null); const onClick = () => { axios.get('https://jsonplaceholder.typicode.com/todos/1').then(response => { setData(response.data); }); }; return ( <div> <div> <button onClick={onClick}>load</button> </div> {data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly={true}/>} </div> ); }; export default App; 불러오기 버튼을 누르면 JSONPlaceholder에서 제공하는 API를 호출하고 이에 대한 응답을 state에 넣어서 보여주는 예제이다.
뉴스 뷰어 예제
/src/components
-> Categories.js : 카테고리 출력
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport React from 'react'; import styled, { css } from 'styled-components'; import { NavLink } from 'react-router-dom'; const categories = [ { name: 'all', text: 'all' }, { name: 'business', text: 'business' }, { name: 'health', text: 'health' }, { name: 'science', text: 'science' }, { name: 'sports', text: 'sports' }, { name: 'technology', text: 'technology' } ]; 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(NavLink) ` font-size: 1.125rem; cursor: pointer; white-space: pre; text-decoration: none; color: inherit; padding-bottom: 0.25rem; &: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} activeClassName="active" exact={c.name === 'all'} to={c.name === 'all' ? '/' : `/${c.name}`} >{c.text}</Category> ))} </CategoriesBlock> ); }; export default Categories; -> NewsItem.js : 단일 기사 출력
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport 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: 0.5rem; white-space: normal; } } & + & { margin-top: 3rem; } `; /* article을 상속받는다. 이 객체의 값들을 따로 빼낸 후, 출력 */ 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 : 기사 리스트 출력
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import NewsItem from './NewsItem'; 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; } `; /* category를 상속받는다. 커스터마이징한 usePromise 함수로 외부 API 통신인 비동기 처리를 한다. category의 값이 변경될 때 동작하며 결과 값은 response에 저장된다. map 함수를 이용해 NewsItem 컴포넌트에 각 기사들을 넘겨준다. */ const NewsList = ({ category }) => { const [loading, response, error] = usePromise(() => { const query = category === 'all' ? '' : `&category=${category}`; return axios.get(`http://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=dfff88bf1d4144c9bb4028ccbe68b428`); }, [category]); if(loading) { return <NewsListBlock>loading...</NewsListBlock> } if(!response) { return null; } if(error) { return <NewsListBlock>error!</NewsListBlock> } const { articles } = response.data; return ( <NewsListBlock> {articles.map(article => ( <NewsItem key={article.url} article={article}/> ))} </NewsListBlock> ); }; export default NewsList; /src/lib
-> usePromise.js : promise 함수를 간결하게 처리하도록 도와준다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport { useState, useEffect } from 'react'; export default function usePromise(promiseCreator, deps) { const [loading, setLoading] = useState(false); const [resolved, setResolved] = useState(null); const [error, setError] = useState(null); useEffect(() => { const process = async () => { setLoading(true); try { const resolved = await promiseCreator(); setResolved(resolved); } catch (e) { setError(e); } setLoading(false); }; process(); }, deps); return [loading, resolved, error]; } /src/pages
-> NewsPage.js : 라우터 적용하도록 도와준다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport React from 'react'; import Categories from '../components/Categories'; import NewsList from '../components/NewsList'; const NewsPage = ({ match }) => { const category = match.params.category || 'all'; return ( <> <Categories/> <NewsList category={category}/> </> ); }; export default NewsPage; /src/App.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport React, { useState, useCallback } from 'react'; import { Route } from 'react-router-dom'; import NewsPage from './pages/NewsPage'; // category 끝에 "?"가 있다면 category가 선택적이라는 것을 의미 const App = () => { return <Route path="/:category?" component={NewsPage}></Route> }; export default App; 'Javascript > React.js' 카테고리의 다른 글
context API (1) 2020.05.13 SPA (0) 2020.05.11 immer (0) 2020.05.06 최적화 방법 (0) 2020.05.06 chrome 개발자 도구를 통한 성능 모니터링 (0) 2020.05.06