react를 다루는 기술

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

crab. 2022. 6. 16. 22:48

비동기 작업의 이해

  • 웹 애플리케이션을 만들다 보면 처리할 때 시간이 걸리는 작업이 있습니다.
  • 예를 들어 웹 애플리케이션에서 서버 쪽 데이터가 필요할 때는 Ajax 기법을 사용하여 서버의 API를 호출함으로써 데이터를 수신합니다.
  • 이렇게 서버의 API를 사용해야 할 때는 네트워크 송수신 과정에서 시간이 걸리기 때문에 작업이 즉시 처리되는 것이 아니라, 응답을 받을 때까지 기다렸다가 전달받은 응답 데이터를 처리합니다.
  • 이 과정에서 해당 작업을 비동기적으로 처리하게 됩니다.

  • 비동기적 처리
    • 요청을 처리하더라도 웹 애플리케이션이 멈추지 않고 동시에 여러 가지 요청을 처리 가능
    • 기다리는 과정에서 여러 함수도 호출 가능
    • 서버 요청외에 특정 작업을 예약할 때
  • 동기적 처리
    • 한 요청이 끝날 때까지 기다리는 동안 중지 상태가 되어 다른 작업을 할 수 없다.
    • 이 요청이 끝나야 다음 작업을 할 수 있음.

콜백함수

  • 이렇게 서버 API를 호출할 때 외에도 작업을 비동기적으로 처리할 때가 있는데, 바로 setTimeout 함수를 사용하여 특정 작업을 예약할 때입니다.
function printMe() {
console.log('Hello World!');
}
setTimeout(printMe, 3000);
console.log('대기 중...');

//대기 중...
//Hello World!
  • setTimeout이 사용되는 시점에서 코드가 3초 동안 멈추는 것이 아니라, 일단 코드가 위부터 아래까지 다 호출되고 3초 뒤에 우리가 지정해 준 printMe가 호출되고 있죠.
  • 자바스크립트에서 비동기 작업을 할 때 가장 흔히 사용하는 방법은 콜백 함수를 사용하는 것입니다.
  • 위 코드에서는 printMe가 3초 뒤에 호출되도록 printMe 함수 자체를 setTimeout 함수의 인자로 전달해 주었는데, 이런 함수를 콜백 함수라고 부릅니다.
function increase(number, callback){
    setTimeout(() => {
        const result = number + 10;
        if(callback){
            callback(result);
        }
    }, 1000)
}

increase(0, result => {
    console.log(result)
    increase(result, result => {
        console.log(result)
        increase(result, result => {
            ...
        })
    })
})
  • 파라미터 값이 주어지면 1초 뒤에 10을 더해서 반환하는 함수가 있다.
  • 단점 : 코드가 여러 번 중첩되어 콜백 지옥에 빠진다.

Promise

  • 콜백 지옥에 빠지지 않게 하기 위한 방법
function increase(number){
    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            const result = number + 10;
            if(result > 50){
                const e = new Error('에러 메세지')
                return reject(e)'
            }
            resolve(result);
        }, 1000)
    });
    retrun promise;
}

increase(0)
.then(number => {
    console.log(number);
    return increase(number)
})
.then(number => {
    console.log(number);
    return increase(number)
})
.then(number => {
    console.log(number)
    return incrase(number)
})
.catch(e => {
    console.log(e)
})
  • 콜백함수를 감싸지 않고 then을 이용하여 다음 작업을 설정한다.

async/await

  • Promise를 더욱 쉽게 사용할 수 있도록 해주는 문법
  • 함수의 앞부분에 async, 해당 함수 내부에서 Promise의 앞부분에 await 키워드 사용
  • Promise가 끝날 때까지 기다리고, 결과 값을 특정 변수에 담을 수 있다.
function increase(number){
    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            const result = number + 10;
            if(result > 50){
                const e = new Error('에러 메세지')
                return reject(e)'
            }
            resolve(result);
        }, 1000)
    });
    retrun promise;
}

async function runTasks() {
    try{
        let result = await incrase(0);
        console.log(result)
        result = await increase(result);
        console.log(result)
        result = await increase(result);
        console.log(result)
    }catch(e){
        console.log(e)
    }
}
  • 예외 처리를 try, catch를 이용해서 처리
  • axios와 같이 Promise 기반으로 처리되는 것이 있다면 그냥 async, await를 사용하면 됨.

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

  • 현재 가장 많이 사용되고 있는 자바스크립트 HTTP 클라이언트.
  • HTTP 요청을 Promise 기반으로 처리한다 -> 바로 async, await 사용 가능
  • 설치
  • yarn add axios
  • 사용
    • axios.get 함수 : 파라미터로 전달된 주소에 GET요청.
    • .then을 이용하여 결과값 response.data를 setData를 통해 설정.
  • const [data, setData] = useState(null); const onClick= () => { axios.get('<https://jsonplaceholder.typicode.com/todos/1>').then(resoponse=>{ setData(response.data); }) }
  • async, await 사용
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)
    }
}

newsapi API 키 발급받기

  • newsapi에서 제공하는 API를 사용하기 위해 키 발급
  • 클릭하면 row 데이터 생성하는 코드
  const onClick = async () => {
    try {
      const response = await axios.get(
        `http://newsapi.org/v2/top-headlines?country=kr&apiKey=74e7b5b66333419989303e6f693d732e`,
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };

뉴스 뷰어 UI 만들기

  • 스타일드컴포넌트를 설치한다.
  • 뉴스데이터에는 아래 데이터들이 있다.
    • title: 제목
    • description: 내용
    • url: 링크
    • urlToImage: 뉴스 이미지
  • NewsItem에 넘겨줄 props 설정 (title, description, url, urlToImage)
  • 지금은 아직 데이터를 불러오지 않고 있으니
  • sampleArticle이라는 객체에 미리 예시 데이터를 넣은 후 각 컴포넌트에 전달하여 가짜 내용이 보이게 해 보세요.
const sampleArticle = {
title: '제목',
description: '내용',
url: '<https://google.com>',
urlToImage: '<https://via.placeholder.com/160>',
};

const NewsList = () => {
return (
 
)

데이터 연동하기

  • useEffect를 사용하여 컴포넌트가 처음 렌더링되는 시점에 API를 요청하면 됩니다.
  • 주의할 점은 useEffect에 등록하는 함수에 async를 붙이면 안 된다는 것
  • useEffect 내부에서 async/await를 사용하고 싶다면, 함수 내부에 async 키워드가 붙은 또 다른 함수를 만들어서 사용해 주어야 합니다.
  • 요청이 대기 중일 때는 loading 값이 true가 되고, 요청이 끝나면 loading 값이 false가 되어야 합니다.

카테고리 지정하기

const NewsList = ( {category} ) => {

useEffect(() => {
const fetchData = async () => {
setLoading(true);
    try {
        const query = category === 'all' ? '' : `&category=${category}`
        const response = await axios.get(
        `http://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=74e7b5b66333419989303e6f693d732e`,
    );
        setArticles(response.data.articles);
    } catch (e) {
        console.log(e);
    }
setLoading(false);
};
fetchData();
}, [category]);
}
  • query 변수를 통해 카테고리 별 API 호출
  • useEffect의 두 번째 파라미터에 category 설정
  • App에서 category 상태를 useState로 관리하겠습니다.
  • 추가로 category 값을 업데이트하는 onSelect라는 함수도 만들어 주겠습니다. 그러고 나서
  • category와 onSelect 함수를 Categories 컴포넌트에게 props로 전달해 주세요.
  • 또한, category 값을 NewsList 컴포넌트에게도 전달해 주어야 합니다.
  • Categories에서는 props로 전달받은 onSelect를 각 Category 컴포넌트의 onClick으로 설정해 주고,
  • 현재 선택된 카테고리 값에 따라 다른 스타일을 적용시켜 보세요.
  • 현재 category 값이 무엇인지에 따라 요청할 주소가 동적으로 바뀌고 있습니다.
  • category 값이 all이라면 query 값을 공백으로 설정하고, all이 아니라면 "&category=카테고리" 형태의 문자열을 만들도록 했습니다.
  • 그리고 이 query를 요청할 때 주소에 포함시켜 주었습니다.
  • 추가로 category 값이 바뀔 때마다 뉴스를 새로 불러와야 하기 때문에 useEffect의 의존 배열(두 번째 파라미터로 설정하는 배열)에 category를 넣어 주어야 합니다.

NavLink 사용하기

  • 선택된 카테고리에 다른 스타일을 주는 기능
<CategoriesBlock
    key = {c.name}
    activeClassName="active"
    exact={c.name==='all'}
    to={c.name === 'all' ? '/' : `/${c.name}`}
/>