[리액트를 다루는 기술] 14장 외부 API를 연동하여 뉴스 뷰어 만들기

  • https://newsapi.org/ 에서 news API 가져오기

  • 9장의 style-componenets 활용하여 프로젝트 스타일링하기

  • 실습진행

    비동기작업의 이헤 - axios로 API호출해서 데이터 받기 - newssapi API 키 발급받기 - 뉴스 뷰어 ui 만들기 - 데이터 연동하기 - 카테고리 기능 구현 - 리액트 라우터적용

14.1 비동기 작업의 이해

  • 서버 API 호출하면 송수신 과정에 시간이 오래걸림 -> 비동기처리로 진행
  • 비동기 작업시 주로 -> 콜백함수(인자로 전달되는 함수), setTimeout() 함수 사용

14.1.1 콜백 함수

function increase(number, callback) {
  setTimeout(() => {
    const result = number + 10;
    if (callback) {
      callback(result);
    }
  }, 1000);
}

increase(0, (result) => {
  console.log(result);
});
  • 하지만 콜백지옥에 빠질수 있음(중첩 또 중첩..) -> 가독성이 나쁨으로 지양하셈

14.1.2 Promise

  • 콜백지옥같은 코드형성 방지용으로 ES6에 도입됨
function increase(number) {
  const promise = new Promise((resolve, reject) => {
    // resolve는 성공, reject는 실패
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        // 50보다 높으면 에러 발생시키기
        const e = new Error("NumberTooBig");
        return reject(e);
      }
      resolve(result); // number 값에 +10 후 성공 처리
    }, 1000);
  });
  return promise;
}

increase(0)
  .then((number) => {
    // Promise에서 resolve된 값은 .then을 통해 받아 올 수 있음
    console.log(number);
    return increase(number); // Promise를 리턴하면
  })
  .then((number) => {
    // 또 .then으로 처리 가능
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .catch((e) => {
    // 도중에 에러가 발생한다면 .catch를 통해 알 수 있음
    console.log(e);
  });

function increase(number) {
  const promise = new Promise((resolve, reject) => {
    // resolve는 성공, reject는 실패
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        // 50보다 높으면 에러 발생시키기
        const e = new Error("NumberTooBig");
        return reject(e);
      }
      resolve(result); // number 값에 +10 후 성공 처리
    }, 1000);
  });
  return promise;
}

increase(0)
  .then((number) => {
    // Promise에서 resolve된 값은 .then을 통해 받아 올 수 있음
    console.log(number);
    return increase(number); // Promise를 리턴하면
  })
  .then((number) => {
    // 또 .then으로 처리 가능
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .catch((e) => {
    // 도중에 에러가 발생한다면 .catch를 통해 알 수 있음
    console.log(e);
  });
  • 중첩하지 않고 해결할수 있음

14.1.3 async/await

  • async/await는 Promise를 더 쉽게 사용할 수 있도록 해 주는 ES2017(ES8) 문법

  • 문법을 사용을 위해..

    • 함수의 앞부분에 async 키워드를 추가
    • 해당 함수 내부 Promise의 앞부분에 await 키워드를 사용
function increase(number) {
  const promise = new Promise((resolve, reject) => {
    // resolve는 성공, reject는 실패
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        // 50보다 높으면 에러 발생시키기
        const e = new Error("NumberTooBig");
        return reject(e);
      }
      resolve(result); // number 값에 +10 후 성공 처리
    }, 1000);
  });
  return promise;
}

async function runTasks() {
  try {
    // try/catch 구문을 사용하여 에러를 처리합니다.
    let result = await increment(0);
    console.log(result);
    result = await increment(result);
    console.log(result);
    result = await increment(result);
    console.log(result);
    result = await increment(result);
    console.log(result);
    result = await increment(result);
    console.log(result);
    result = await increment(result);
    console.log(result);
  } catch (e) {
    console.log(e);
  }
}

14.2 axios로 API 호출해서 데이터 받아 오기

  • axios 란?

    현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트
    라이브러리의 특징은 HTTP 요청을 Promise 기반으로 처리한다는 점

다음과 같이 설치

npm create react-app news-viewer
cd news-viewer
npm add axios
  • .prettierrc (자동정렬설정)
{
    "singleQuote": true,
    "semi": true,
    "useTabs": false,
    "tabWidth": 2,
    "trailingComma": "all",
    "printWidth": 80
  }
  • jsconfig.json (파일자동불러오기)
{
    "compilerOptions": {
      "target": "es6"
    }
  }
  • App.js 다음과 같이 작성
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}>불러오기</button>
      </div>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};

export default App;
  • onClick 함수에서 axios.get 함수를 사용 -> 전달주소에 GET 요청을 해줌

  • .then을 통해 비동기적으로 확인 가능

  • async 적용하기


import React, { useState } from 'react';
import axios from 'axios';

const App = () => {
  const [data, setData] = useState(null);
  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/todos/1',
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    } // async 를 적용
  };
  return (
    <div>
      <div>
        <button onClick={onClick}>불러오기</button>
      </div>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};

export default App;

14.3 newsapi API 키 발급받기

14.4 뉴스 뷰어 UI 만들기

  • styled components 설치
npm add styled-components
  • src 디렉토리에 두개의 파일 생성
    • NewsItem.js : 뉴스정보를 보여주는 컴포넌트
    • NewsList.js : API 요청 -> 컴포넌트 배열로 변환 및 렌더링

14.4.1 NewsItem 만들기

  • 뉴스데이터는 -> JSON 객체
  • 다음과 같이 만들어보자 !
• title: 제목
• description: 내용
• url: 링크
• urlToImage: 뉴스 이미지
  • 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: 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; 

14.4.2 NewsList 만들기

  • NewsList.js -> sampleArticle (가짜데이터)를 넣음
import React from 'react';
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 React from 'react';
import NewsList from './components/NewsList';


const App = () => {
  return <NewsList />;
};



export default App;
  • 오류발생(추후 수정예정)

14.5 데이터 연동하기

  • API 호출하기 -> useEffect 사용 -> useEffect 등록함수에 async를 붙이면 안됨(뒷정리함수라)
  • map 함수 사용하여 컴포넌트 배열로 변환전 !article 을 조회하여 null이 아닌지 검사 -> map함수가 없기때문에 렌더링 오류걸릴수 있음

14.6 카테고리 기능 구현하기

  • 아래와 같은 카테고리 구현
business(비즈니스)entertainment(연예)health(건강)science(과학)sports(스포츠)technology(기술)

14.6.1 카테고리 선택 UI 만들기

  • Categories.js 생성
import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';

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;
  @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}
          className={({ isActive }) => (isActive ? 'active' : undefined)}
          to={c.name === 'all' ? '/' : `/${c.name}`}
        >
          {c.text}
        </Category>
      ))}
    </CategoriesBlock>
  );
};

export default Categories;
  • App.js 렌더링후
  • category 상태를 useState로 관리

14.7 리액트 라우터 적용하기

  • 기존 카테고리 값을 라우터의 URL 파라미터를 사용하여 관리

14.7.1 리액트 라우터의 설치 및 적용

  • 리액트 라우터 설치 및 적용
npm add react-router-dom 

14.9 정리

  • 사용해야 할 API의 종류가 많아지면 요청을 위한 상태 관리를 하는 것이 번거로워질 수 있음
  • 리덕스와 리덕스 미들웨어를 배우면 좀 더 쉽게 요청에 대한 상태를 관리할 수 있음

좋은 웹페이지 즐겨찾기