ABOUT ME

Today
Yesterday
Total
  • 외부 API를 연동해 뉴스 뷰어 만들기
    Javascript/React.js 2020. 5. 12. 20:21

    axios

    현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트이다. Promise 기반으로 처리한다.

     

    예시

    import 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;
    view raw ex.js hosted with ❤ by GitHub

    불러오기 버튼을 누르면 JSONPlaceholder에서 제공하는 API를 호출하고 이에 대한 응답을 state에 넣어서 보여주는 예제이다.

    뉴스 뷰어 예제

    /src/components

    -> Categories.js : 카테고리 출력

    import 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;
    view raw ex.js hosted with ❤ by GitHub

    -> 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: 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;
    view raw ex.js hosted with ❤ by GitHub

    -> NewsList.js : 기사 리스트 출력

    import 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;
    view raw e.js hosted with ❤ by GitHub

     

    /src/lib

    -> usePromise.js : promise 함수를 간결하게 처리하도록 도와준다.

    import { 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];
    }
    view raw ex.js hosted with ❤ by GitHub

     

    /src/pages

    -> NewsPage.js : 라우터 적용하도록 도와준다.

    import 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;
    view raw ex.js hosted with ❤ by GitHub

     

    /src/App.js

    import 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;
    view raw app.js hosted with ❤ by GitHub

    '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
Designed by Tistory.