리액트를 다루는 기술, 외부 API를 연동하여 뉴스 뷰어 만들기
[ 14.1 비동기 작업의 이해 ]
웹 애플리케이션을 만들다 보면 처리할 때 시간이 걸리는 작업이 있음 예를 들어 웹 애플리케이션에서 서버 쪽 데이터가 필요할 때는 Ajax 기법을 사용하여 서버의 API를 호출함으로써 데이터를 수신함 서버의 API를 사용해야 할 때는 네트워크 송수신 과정에서 시간이 걸리기 때문에 작업이 즉시 처리되는 것이 아니라 응답을 받을때까지 기다렸다가 전달받은 응답 데이터를 처리함 이과정에서 해당 작업을 비동기적으로 처리하게 됨
만약 작업을 동기적으로 처리한다면 요청을 끝날때까지 기다리는 동안 중지 상태가 되기 때문에 다른 작업을 할 수 없음
요청이 끝나야 비로소 그 다음 예정된 작업을 할 수 있음 하지만 이를 비동기적으로 처리하며 웹 애플리케이션이 멈추지 않고 동시에 여러 가지 요청을 처리할 수도 있고 기다리는 과정에서 다른 함수도 호출할 수 있음
서버 API를 호출할 때 외에도 작업을 비동기적으로 처리할 때가 있는데 바로 setTimeout함수를 사용하여 특정 작업을 예약할 때임
JS에서 비동기 작업을 할 때 가장 흔히 사용하는 방법은 콜백 함수를 사용하는 것 함수자체를 함수의 인자로 들어간 함수를 콜백함수라고 함
▶ 콜백함수
function increase(number, callback) {
setTimeout(() => {
const result = number + 10;
if (callback) {
callback(result);
}
}, 1000);
}
increase(0, (result) => {
console.log(result);
});
해당 함수가 처리된 직후 어떤한 작업을 하고 싶다면 콜백 함수를 활용해서 작업함 여러개를 순차적으로 처리하고 싶다면 콜백함수를 중첩하여 구현할 수 있음
increase(0, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
increase(result, (result) => {
console.log(result);
});
});
});
});
하지만 이렇게 콜백을 여러번 중첩하면 코드의 가독성이 나빠짐 이러한 형태의 코드를 '콜백지옥'이라고 부름
웬만하면 지양해야 할 형태의 코드
▶ 14.1.2 Promise
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);
}, 1000);
});
return promise;
}
increase(0)
.then((number) => {
// Promise에서 resolve된 값은 .then을 통해 받아 올 수 있음
console.log(number);
return increase(number); // Promise를 리턴
})
.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);
})
.then((number) => {
console.log(number);
return increase(number);
})
.catch((e) => {
//도중에 에러가 발생한다면 .catch를 통해 알 수 있음
console.log(e);
});
여러 작업을 연달아 처리한다고 해서 함수를 여러 번 감싸는 것이 아니라 .then을 사용하여 그다음 작업을 설정하기 때문에 콜백 지옥이 형성되지 않음
▶ 14.1.3 async/await
async/await는 Promise를 더욱 쉽게 사용할 수 있도록 해주는 ES2017(ES8) 문법
이 문법을 사용하려면 함수의 앞부분에 async 키워드를 추가하고 해당 함수 내부에서 Promise의 앞부분에 await 키워드를 사용함 이렇게 하면 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);
}
}
[ 14.2 axios로 API 호출해서 데이터 받아오기 ]
axios는 현재 가장 많이 사용되고 있는 JS HTTP 클라이언트
이 라이브러리의 특징은 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}>불러오기</button>
</div>
{data && (
<textarea
rows={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</div>
);
};
export default App;
불러오기 버튼을 누르면 JSONPlaceholder에서 제공하는 가짜 API를 호출하고 이에 대한 응답을 컴포넌트 상태에 넣어서 보여줌 onClick 함수에서 axios.get 함수를 사용 이 함수는 파라미터로 전달된 주소에 GET 요청을 해줌
이에 대한 결과는 .then을 통해 비동기적으로 확인할 수 있음
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);
}
};
return (
<div>
<div>
<button onClick={onClick}>불러오기</button>
</div>
{data && (
<textarea
rows={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</div>
);
};
export default App;
위에 예시에 async를 적용하면 이런 코드가 됨
화살표 함수에 async/await를 적용할 때는 async () => {}와 같은 형식으로 적용됨
[ 14.3 newsapi API 키 발급받기 ]
사전에 newsapi에서 API를 키를 발급받음 API 키는 https://newsapi.org/register에 가입하면 발급 받을 수 있음
https://newsapi.org/s/south-korea-news-api로 들어가면 API 설명서가 있음
사용할 API 주소는 두가지 형태임
- 전체 뉴스 불러오기
- GET https://newsapi.org/v2/top-headlines?country=kr&apiKey=발급받은 키
- 특정 카테고리 뉴스 불러오기
- GET https://newsapi.org/v2/top-headlines?country=kr&category=businessapiKey=발급받은 키
여기 카테고리는 business, entertainment, health, science, sports, technology 중에 골라서 사용할 수 있음
카테고리를 생략하면 모든 카테고리의 뉴스를 불러옴 apiKey 값에는 발급받은 API 키를 입력하면 됨
import React, { useState } from 'react';
import axios from 'axios';
const App = () => {
const [data, setData] = useState(null);
const apiKey = process.env.REACT_APP_APIKEY;
console.log(apiKey);
const onClick = async () => {
try {
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${apiKey}`,
);
setData(response.data);
} catch (e) {
console.log(e);
}
};
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.4 뉴스 뷰어 UI 만들기 ]
▶ 14.4.1 NewsItem 만들기
각 뉴스 데이터에 필드를 확인해보면
{
"source": {
"id": null,
"name": "Khan.co.kr"
},
"author": "경향신문",
"title": "'금태섭 신당', 원희룡에 “맘에 안 든다고 '올스톱' 할 거면 정치가 왜 필요한가” - 경향신문",
"description": null,
"url": "https://www.khan.co.kr/politics/politics-general/article/202307101450011",
"urlToImage": null,
"publishedAt": "2023-07-10T05:50:00Z",
"content": null
}
// src/components/NewsItem.js
import React from 'react';
import { styled } from 'styled-components';
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>
);
};
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;
}
`;
export default NewsItem;
// App.js
import React from 'react';
import NewsList from './components/NewsList';
const App = () => {
return <NewsList />;
};
export default App;
[ 14.5 데이터 연동하기 ]
useEffect를 사용하여 컴포넌트가 처음 렌더링되는 시점에 API 요청
주의할점은 useEffect에 등록하는 함수에 async를 붙이면 안됨 useEffect에서 반환해야 하는 값은 뒷정리 함수이기 때문
따라서 useEffect 내부에서 async/await를 사용하고 싶다면 함수 내부에 async 키워드가 붙은 또 다른 함수를 만들어사 사용해 주어야함 또한 loading이라는 상태를 관리하여 API 요청이 대기 중인지 판별 요청이 대기중일때는 loading값이 true가 되고 요청이 끝나면 loading 값이 false가 되어야 함
// src/components/NewsList.js
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import axios from 'axios';
import NewsItem from './NewsItem';
const NewsList = () => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
const apiKey = process.env.REACT_APP_APIKEY;
useEffect(() => {
//async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${apiKey}`,
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
// 대기중일때
if (loading) {
return <NewsListBlock>대기중...</NewsListBlock>;
}
if (!articles) {
return null;
}
// articles 값이 유효할때
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
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;
}
`;
export default NewsList;
데이터를 불러와서 배열을 map 함수를 사용하여 컴포넌트 배열로 변환할 때 신경써야 할 부분이 있음
map 함수를 사용하기 전에 꼭 !articles를 조회하여 해당 값이 현재 null이 아닌지 검사해야 함
이 작업을 하지 않으면 아직 데이터가 없을 때 null에는 map 함수가 없기 때문에 렌더링 과정에서 오류가 발생함
그래서 애플리케이션이 제대로 나타나지 않고 흰 페이지만 보이게 됨
[ 14.6 카테고리 기능 구현하기 ]
// src/components/Categories.js
import React from 'react';
import styled from 'styled-components';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비즈니스',
},
{
name: 'entertainment',
text: '엔터테이먼트',
},
{
name: 'health',
text: '건강',
},
{
name: 'science',
text: '과학',
},
{
name: 'sports',
text: '스포츠',
},
{
name: 'technology',
text: '기술',
},
];
const Categories = () => {
return (
<CategoriesBlock>
{categories.map((item) => (
<Category key={item.name}>{item.text}</Category>
))}
</CategoriesBlock>
);
};
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.div`
font-size: 1.125rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
}
& + & {
margin-left: 1rem;
}
`;
export default Categories;
categories라는 배열 안에 name과 text 값이 들어가 있는 객체들을 넣어 주어서 한글로 된 카테고리와 실제 카테고리 값을 연결시켜 줌 여기서 name은 카테고리 값을 가리키고 text값은 레더링할 때 사용할 한글 카테고리를 가리킴
// App.js
import React, { useCallback, useState } from 'react';
import NewsList from './components/NewsList';
import Categories from './components/Categories';
const App = () => {
const [category, setCategory] = useState('all');
const onSelect = useCallback((category) => setCategory(category), []);
return (
<>
<Categories category={category} onSelect={onSelect} />
<NewsList category={category} />
</>
);
};
export default App;
App에서 category 상태를 useState로 관리함 추가로 category 값을 업데이트하는 onSelect라는 함수를 만들어줌
category와 onSelect 함수를 Categories 컴포넌트에 props로 전달하고 category값을 NewsList 컴포넌트에게도 전달
import React, { useCallback, useState } from 'react';
import NewsList from './components/NewsList';
import Categories from './components/Categories';
const App = () => {
const [category, setCategory] = useState('all');
const onSelect = useCallback((category) => setCategory(category), []);
return (
<>
<Categories category={category} onSelect={onSelect} />
<NewsList category={category} />
</>
);
};
export default App;
// src/components/Categories.js
import React from 'react';
import styled, { css } from 'styled-components';
const categories = [
{
name: 'all',
text: '전체보기',
},
{
name: 'business',
text: '비즈니스',
},
{
name: 'entertainment',
text: '엔터테이먼트',
},
{
name: 'health',
text: '건강',
},
{
name: 'science',
text: '과학',
},
{
name: 'sports',
text: '스포츠',
},
{
name: 'technology',
text: '기술',
},
];
const Categories = ({ onSelect, category }) => {
return (
<CategoriesBlock>
{categories.map((item) => (
<Category
key={item.name}
active={category === item.name}
onClick={() => onSelect(item.name)}
>
{item.text}
</Category>
))}
</CategoriesBlock>
);
};
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.div`
font-size: 1.125rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.25rem;
&:hover {
color: #495057;
}
${(props) =>
props.active &&
css`
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
`}
& + & {
margin-left: 1rem;
}
`;
export default Categories;
props로 전달받은 onSelect를 각 Category컴포넌트의 onClick으로 설정해주고 현재 선택된 카테고리 값에 따라 다른 스타일 적용
▶ 14.6.2 API를 호출할 때 카테고리 지정하기
props로 받아 온 category에 따라 카테고리를 지정하여 API를 요청
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import axios from 'axios';
import NewsItem from './NewsItem';
const NewsList = ({ category }) => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
const apiKey = process.env.REACT_APP_APIKEY;
useEffect(() => {
//async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true);
try {
const query = category === 'all' ? '' : `&category=${category}`;
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=${apiKey}`,
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
// 대기중일때
if (loading) {
return <NewsListBlock>대기중...</NewsListBlock>;
}
if (!articles) {
return null;
}
// articles 값이 유효할때
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
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;
}
`;
export default NewsList;
category값이 바뀔때 마다 요청할 주소가 동적으로 바뀜
category값이 바뀔때마다 뉴스를 새로 불러와야 하기 때문에 useEffect의 의존배열에 category를 넣어 주어야 함
[ 14.7 리액트 라우터 적용하기 ]
값을 리액트 라우터의 URL 파라미터를 사용하여 관리
▶ 14.7.1 리액트 라우터의 설치 및 적용
터미널로 리액트 라우터 설치
yarn add react-router-dom
index.js에서 리액트 라우터를 적용
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
reportWebVitals();
▶ 14.7.2 NewsPage 생성
import React from 'react';
import { useParams } from 'react-router-dom';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
const NewsPage = ({ match }) => {
const params = useParams();
// 카테고리가 선택되지 않았으면 기본값 all로 사용
const category = params.category || 'all';
return (
<>
<Categories />
<NewsList category={category} />
</>
);
};
export default NewsPage;
현재 선택된 category 값을 URL 파라미터를 통해 사용할 것이므로 Categories 컴포넌트에서 현재 선택된 카테고리 값을 알려 줄 필요도 없고 onSelect 함수를 따로 전달해 줄 필요도 없음
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import NewsPage from './pages/NewsPage';
const App = () => {
return (
<Routes>
<Route path="/" element={<NewsPage />} />
<Route path="/:category" element={<NewsPage />} />
</Routes>
);
};
export default App;
// src/components/NewsList.js
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import axios from 'axios';
import NewsItem from './NewsItem';
const NewsList = ({ category }) => {
const [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(false);
const apiKey = process.env.REACT_APP_APIKEY;
useEffect(() => {
//async를 사용하는 함수 따로 선언
const fetchData = async () => {
setLoading(true);
try {
const query = category === 'all' ? '' : `&category=${category}`;
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=${apiKey}`,
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
// 대기중일때
if (loading) {
return <NewsListBlock>대기중...</NewsListBlock>;
}
if (!articles) {
return null;
}
// articles 값이 유효할때
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
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;
}
`;
export default NewsList;
[ 14.8 usePromise 커스텀 Hook 만들기 ]
컴포넌트에서 API 호출처럼 Promise를 사용해야 하는 경우 더욱 간결하게 코드를 작성할 수 있도록 해 주는 커스텀 Hook
// src/lib/usePromise.js
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();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return [loading, resolved, error];
}
프로젝트의 다양한 곳에서 사용될 수 있는 유틸 함수들은 보통 src 디렉터리에 lib 디렉터리를 만든 후 작성함
usePromise Hook은 Promise의 대기중, 완료결과, 실패 결과에 대한 상태를 관리하며 usePromise의 의존 배열 deps를 파라미터로 받아 옴 파라미터로 받아 온 deps 배열은 usePromise 내부에서 사용한 useEffect의 의존 배열로 설정되는데 이 배열을 설정하는 부분에서 ESLint 경고가 나타남
이 경고를 무시하려면 특정 줄에서만 ESLint 규칙을 무시하도록 주석을 작성해야함
에디터에 초록색 경고 줄이 그어졌을 때 그 위에 커서를 올리면 빠른 수정... 이라는 문구가 나타나는데 이를 클릭하면 자동으로 ESLint 규칙을 비활성화시키는 주석을 입력할 수 있음
// src/components/NewsList.js
import React 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;
}
`;
const NewsList = ({ category }) => {
const [loading, response, error] = usePromise(() => {
const query = category === 'all' ? '' : `&category=${category}`;
return axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=0a8c4202385d4ec1bb93b7e277b3c51f`,
);
}, [category]);
// 대기중일 때
if (loading) {
return <NewsListBlock>대기중...</NewsListBlock>;
}
// 아직 response 값이 설정되지 않았을 때
if (!response) {
return null;
}
// 에러가 발생했을 때
if (error) {
return <NewsListBlock>에러 발생!</NewsListBlock>;
}
// response 값이 유효할 때
const { articles } = response.data;
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
// src/components/NewsList.js
import React 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;
}
`;
const NewsList = ({ category }) => {
const apiKey = process.env.REACT_APP_APIKEY;
const [loading, response, error] = usePromise(() => {
const query = category === 'all' ? '' : `&category=${category}`;
return axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=${apiKey}`,
);
}, [category]);
// 대기중일 때
if (loading) {
return <NewsListBlock>대기중...</NewsListBlock>;
}
// 아직 response 값이 설정되지 않았을 때
if (!response) {
return null;
}
// 에러가 발생했을 때
if (error) {
return <NewsListBlock>에러 발생!</NewsListBlock>;
}
// response 값이 유효할 때
const { articles } = response.data;
return (
<NewsListBlock>
{articles.map((article) => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
);
};
export default NewsList;
usePromise를 사용하면 NewsList에서 대기 중 상태 관리와 useEffect 설정을 직접 하지 않아도되므로 코드가 훨씬 간편해짐 요청 상태를 관리할 때 무조건 커스텀 Hook을 만들어사 사용해야하는 것은 아니지만 상황에 따라 적절히 사용하면 좋은 코드를 만들어 갈 수 있음
[ 14.9 정리 ]
리액트 컴포넌트에서 API를 연동하여 개발할 때 절대 잊지 말아야 할 유의 사항은 useEffect에 등록하는 함수는 async로 작성하면 안된다는 점 함수 내부에 async 함수를 따로 만들어 주어야 함
커스텀 Hook을 만들어 사용함으로써 코드가 간결해지기는 했지만 사용해야 할 API의 종류가 많아지면 요청을 위한 상태 관리를 하는 것이 번거로워질 수 있음 이런 경우 리덕스와 리덕스 미들웨어를 이용하면 좀 더 쉽게 요청에 대한 상태를 관리할 수 있음