일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 자동차수리시스템
- 사용자예외클래스생성
- GRANT VIEW
- 오라클
- 제네릭
- 인터페이스
- cursor문
- 컬렉션 타입
- 컬렉션프레임워크
- 예외미루기
- EnhancedFor
- 환경설정
- 자바
- 대덕인재개발원
- 한국건설관리시스템
- 객체 비교
- 정수형타입
- 생성자오버로드
- 예외처리
- 다형성
- 참조형변수
- 어윈 사용법
- 집합_SET
- NestedFor
- 추상메서드
- 메소드오버로딩
- Java
- oracle
- exception
- abstract
- Today
- Total
거니의 velog
(4) 외부 API를 연동하여 뉴스 뷰어 만들기 4 본문
7. 리액트 라우터 적용하기
* 방금 진행한 뉴스 뷰어 프로젝트에 리액트 라우터를 적용해 보자. 기존에는 카테고리 값을 useState로 관리했는데, 이번에는 이 값을 리액트 라우터와 URL 파라미터를 사용하여 관리해 보자.
(1) 리액트 라우터의 설치 및 적용
* 우선 현재 프로젝트에 리액트 라우터를 설치하자.
$ yarn add react-router-dom
// v5 설치 용도
$ yarn add react-router-dom@5
* 그리고 index.js에서 리액트 라우터를 적용하자.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from '../node_modules/react-router-dom/cjs/react-router-dom.min';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
(2) NewsPage 생성
* 이번 프로젝트에 리액트 라우터를 적용할 때 만들어야 할 페이지는 단 하나이다. src 디렉터리에 pages라는 디렉터리를 생성하고, 그 안에 NewsPage.js 파일을 만들어서 다음과 같이 작성해 보자.
import React from 'react';
import Categories from '../components/Categories';
import NewsList from '../components/NewsList';
const NewsPage = ({ match }) => {
// 카테고리가 선택되지 않았으면 기본값 all로 사용
const category = match.params.category || 'all';
return (
<>
<Categories />
<NewsList category={category} />
</>
);
};
export default NewsPage;
* 현재 선택된 category 값을 URL 파라미터를 통해 사용할 것이므로 Categories 컴포넌트에서 현재 선택된 카테고리 값을 알려 줄 필요도 없고, onSelect 함수를 따로 전달해 줄 필요도 없다.
* 다 만들었으면 App의 기존 내용을 모두 지우고 Route를 정의해 주자.
import React from 'react';
import { Route } from '../node_modules/react-router-dom/cjs/react-router-dom.min';
import NewsPage from './pages/NewsPage';
const App = () => {
return <Route path="/:category?" component={NewsPage} />;
};
export default App;
* 위 코드에서 사용된 path에 /:category?와 같은 형태로 맨 뒤에 물음표 문자가 들어가 있는데, 이는 category 값이 선택적(optional)이라는 의미이다. 즉, 있을 수도 있고 없을 수도 있다는 뜻이다. category URL 파라미터가 없다면 전체 카테고리를 선택한 것으로 간주한다.
(3) Categories에서 NavLink 사용하기
* 이제 Categories에서 기존의 onSelect 함수를 호출하여 카테고리를 선택하고, 선택된 카테고리에 다른 스타일을 주는 기능을 NavLink로 대체해 보겠다. div, a, button, input 처럼 일반 HTML 요소가 아닌 특정 컴포넌트에 styled-components를 사용할 때는 styled(컴포넌트 이름)`` 과 같은 형식을 사용한다.
[Categories.js]
import React from 'react';
import styled, { css } from 'styled-components';
import { NavLink } from '../../node_modules/react-router-dom/cjs/react-router-dom.min';
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 all 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 = ({ onSelect, category }) => {
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;
* NavLink로 만들어진 Category 컴포넌트에 to 값은 "/카테고리이름"으로 설정해 주었다. 그리고 카테고리 중에서 전체보기의 경우는 예외적으로 "/all" 대신에 "/"로 설정했다. to 값이 "/"를 가리키고 있을 떄는 exact 값을 true로 설정해 주어야 한다. 이 값을 설정하지 않으면, 다른 카테고리가 선택되었을 때도 전체보기 링크에 active 스타일이 적용되는 오류가 발생한다.
* 작업을 마쳤다면, 카테고리를 클릭할 때 페이지 주소가 바뀌고 이에 따라 뉴스 목록을 잘 보여 주는지 확인해 보자.
* 이제 구현해야 할 기능을 모두 완성했다!
8. usePromise 커스텀 Hook 만들기
* 이번에는 컴포넌트에서 API 호출처럼 Promise를 사용해야 하는 경우 더욱 간결하게 코드를 작성할 수 있도록 해 주는 커스텀 Hook을 만들어서 우리 프로젝트에 적용해 보자.
* 우리가 만들 Hook의 이름은 usePromise이다. src 디렉터리에 lib 디렉터리를 만들고, 그 안에 usePromise.js를 다음과 같이 작성해 보자.
import { useEffect, useState } 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) {
console.log(e);
}
setLoading(false);
};
process();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return [loading, resolved, error];
}
* 프로젝트의 다양한 곳에서 사용될 수 있는 유틸 함수들은 보통 이렇게 src 디렉터리에 lib 디렉터리를 만든 후 그 안에 작성한다.
* 방금 만든 usePromise Kooh은 Promise의 대기 중, 완료 결과, 실패 결과에 대한 상태를 관리하며, usePromise의 의존 배열 deps를 파라미터로 받아 온다. 파라미터로 받아 온 deps 배열은 usePromise 내부에서 사용한 useEffect 의 의존 배열로 설정되는데, 이 배열을 설정하는 부분에서 ESLint 경고가 나타나게 된다.
* 이 경고를 무시하려면 특정 줄에서만 ESLint 규칙을 무시하도록 주석을 작성해 주어야 한다. 에디터에 초록색 경고 줄이 그어졌을 때 그 위에 커서를 올리면 빠른 수정...이라는 문구가 나타나는데, 이를 클릭하면 자동으로 ESLint 규칙을 비활성화하는 주석을 입력할 수 있다.
* 코드를 저장한 뒤 NewsList 컴포넌트에서 usePromise를 사용해 보자.
[NewsList.js]
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';
import axios from '../../node_modules/axios/index';
import usePromise from '../lib/usePromise';
const NewsListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 0 auto;
margin-top: 2rem;
a {
color: #333;
}
@media all 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=8aa6de08f72745be9bbb2a4e91ef45ae`,
);
}, [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을 만들어서 사용해야 하는 것은 아니지만, 상황에 따라 적절히 사용하면 좋은 코드를 만들어 갈 수 있다.
- http://localhost:3000/entertainment
9. 정리
* 이 장에서는 외부 API를 연동하여 사용하는 방법을 알아보고, 지금까지 배운 것을 활용하여 실제로 쓸모 있는 프로젝트를 개발해 보았다. 리액트 컴포넌트에서 API를 연동하여 개발할 때 절대 잊지 말아야 할 유의 사항은 useEffect에 등록하는 함수는 async로 작성하면 안 된다는 점이다. 그 대신 함수 내부에 async 함수를 따로 만들어 주어야 한다.
* 지금은 usePromise라는 커스텀 Hook을 만들어 사용함으로써 코드가 조금 간결해지기는 했지만, 나중에 사용해야 할 API의 종류가 많아지면 요청을 위한 상태 관리를 하는 것은 번거로워질 수 있다. 뒤에 나올 리덕스와 리덕스 미들웨어를 배우면 좀 더 쉽게 요청에 대한 상태를 관리할 수 있다.
'React > React_뉴스 뷰어 앱 만들기' 카테고리의 다른 글
(3) 외부 API를 연동하여 뉴스 뷰어 만들기 3 (0) | 2023.12.12 |
---|---|
(2) 외부 API를 연동하여 뉴스 뷰어 만들기 2 (0) | 2023.12.12 |
(1) 외부 API를 연동하여 뉴스 뷰어 만들기 1 (1) | 2023.12.12 |