Today I Learned ... react.js
🙋♂️ Reference Book
🙋 My Dev Blog
리액트를 다루는 기술 DAY 14
- 외부 API 연동 - 뉴스 뷰어 제작
웹에서 서버 쪽 데이터가 필요할 때에는 - AJAX
기법을 사용하여 서버의 API를 호출하여 데이터 수신 가능.
서버의 API를 사용해야 하는 경우, 네트워크 송수신 과정에서 시간이 걸려서 작업이 즉시 처리되지 않고,
응답을 받을 때까지 기다렸다가 전달받은 응답(response)을 처리함.
-> 비동기적으로 처리함.
동기적 | 비동기적 |
---|---|
순서대로 처리. 다른 작업 할 수 X. | 순서대로 처리하지 않음. 여러 요청 처리 가능. |
콜백 함수
를 사용함.function increase(number, callback) {
setTimeout(() => {
const result = number + 10;
if (callback) {
callback(result);
}
}, 1000)
}
increase(0, result => {
console.log(result);
});
< (1초 후) 10
만약, 1초마다 10, 20, 30, 40과 같이 여러번 순차적으로 처리하고 싶으면
콜백함수를 중첩하면 된다.
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 => {
console.log(result);
increase(result, result => {
console.log(result);
});
});
});
});
콜백 지옥
🙋♂️ 콜백 지옥(callback hell)
콜백 함수를 익명 함수로 전달하는 과정에서 또 다시 콜백 안에 함수 호출이 반복되어
코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상.
function increase(number) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = number + 10;
if (result > 50) {
const e = new Error('NumberTooBig');
return reject(e);
}
resolve(result);
}, 1000);
});
return 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 increase(number);
})
.then(number => {
console.log(number);
return increase(number);
})
.catch(e => {
console.log(e);
});
.then 메서드
를 사용하면 되므로 콜백 지옥이 생성되지 않음.async
키워드를 추가하고, 함수 내부에서 Promise 앞부분에 await
키워드 사용.function increase(number) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = number + 1;
if (result > 50) {
const e = new Error('NumberTooBig');
return reject(e);
}
resolve(result);
}, 1000)
});
return promise;
}
async function runTasks() {
try {
let result = await increase(0);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
result = await increase(result);
console.log(result);
} catch (e) {
console.log(e);
}
}
await
키워드를 붙여줌.axios
로 API 호출하여 데이터 수신✅ axios
App.js
import { useState } from 'react';
import axios from 'axios';
const App = () => {
const [data, setData] = useState(null);
const onClickBtn = () => {
axios.get('https://jsonplaceholder.typicode.com/todos/1').then(response => {
setData(response.data);
});
};
return (
<div>
<div>
<button onClick={onClickBtn}>불러오기</button>
</div>
{data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly={true} />}
</div>
);
}
export default App;
get()안에 URL은 JSON 형식의 파일임.
axios.get 메서드를 이용하여 데이터를 불러오고, then 메서드로 후속처리.
setData(response.data)로 서버가 응답한 데이터를 가져올 수 있음.
참고 - 응답 스키마
{ // `data`는 서버가 제공한 응답(데이터)입니다. data: {}, // `status`는 서버 응답의 HTTP 상태 코드입니다. status: 200, // `statusText`는 서버 응답으로 부터의 HTTP 상태 메시지입니다. statusText: 'OK', // `headers` 서버가 응답 한 헤더는 모든 헤더 이름이 소문자로 제공됩니다. headers: {}, // `config`는 요청에 대해 `axios`에 설정된 구성(config)입니다. config: {}, // `request`는 응답을 생성한 요청입니다. // 브라우저: XMLHttpRequest 인스턴스 // Node.js: ClientRequest 인스턴스(리디렉션) request: {} }
response.data
는 서버가 제공한 응답(=data)을 의미함.axios.get()
메서드 사용..then
을 통해 비동기적으로 확인 가능⚡️ async/await 적용시
import { useState } from 'react';
import axios from 'axios';
const App = () => {
const [data, setData] = useState(null);
const onClickBtn = 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={onClickBtn}>불러오기</button>
</div>
{data && <textarea rows={7} value={JSON.stringify(data, null, 2)} readOnly={true} />}
</div>
);
}
export default App;
https://newsapi.org/v2/top-headlines?country=kr&apiKey=API인증키
를 넣어줌.불러오기를 클릭하면 해당 API의 데이터가 불러와짐.
$ yarn add styled-components
우선 NewsItem.js와 NewsList.js 파일을 생성한 후,
뉴스 데이터에는 어떤 필드가 있는지 확인해보자.
"source": {
"id": null,
"name": "Sbs.co.kr"
},
"author": "서대원 기자",
"title": "고진영, LPGA 투어 팔로스 버디스 챔피언십 2R 공동 2위 - SBS 뉴스",
"description": "여자골프 세계랭킹 1위 고진영 선수가 미국여자프로골프 투어 신설대회인 팔로스 버디스 챔피언십 2라운드에서 공동 2위에 올랐습니다. 고진영은 미국 캘리포니아주 팔로스 버디스 골프클럽에서 열린 대회 이틀째 경기에서 버디 1개와 더블 보기 1개로 1오버파를 쳤습니다.",
"url": "https://news.sbs.co.kr/news/endPage.do?news_id=N1006733776",
"urlToImage": "https://img.sbs.co.kr/newimg/news/20220425/201658611_1280.jpg",
"publishedAt": "2022-04-30T02:23:00Z",
"content": null
},
🙋♂️ styled-component 사용.
주로 컴포넌트를 감싸는
___Block
(=styled.div)를 위에 별도로 생성함.
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;
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} />
<NewsItem article={sampleArticle} />
</NewsListBlock>
)
}
export default NewsList;
article
을 넘겨줌.App.js
import NewsList from './components/NewsList';
const App = () => {
return <NewsList />;
}
export default App;
useEffect
를 이용. 두번째 인자를 [ ] 로 하여 맨 처음에만 실행되도록.loading
이라는 state로 로딩중인지 판별할 수 있게 함.❗️ 주의
- useEffect의 콜백 안에
async
를 붙여선 안된다.- useEffect는 뒷정리 함수를 return 하기 때문.
-> 따라서, 콜백에서async
를 쓰고 싶다면 내부에서 함수를 만들어서 호출해줘야 함.
NewsList.js
// 여기서 API 요청
import { useState, useEffect } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from 'axios';
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 = () => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
'https://newsapi.org/v2/top-headlines?country=kr&apiKey=f6052deb31a34f38b602753e2ddf0daf'
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <NewsListBlock>대기 중...</NewsListBlock>
}
if (!articles) {
return null;
}
return (
<NewsListBlock>
{articles.map(article => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
!articles
일때, 즉 articles가 null 일때를 검사해야 한다.