안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
외부 API를 연동하여 뉴스 뷰어 만들기
리액트 라우터 적용하기
URL 파라미터를 사용하여 카테고리 값들을 관리해보겠습니다.
리액트 라우터의 설치 및 적용
$ 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>
);
index.js에서 리액트 라우터를 적용시켜 주었습니다.
NewsPage 생성
src/pages/NewsPage.js
import { useParams } from "react-router-dom";
import Categories from "../components/Categories";
import NewsList from "../components/NewsList";
const NewsPage = () => {
const params = useParams();
// 카테고리가 선택되어있지 않다면 기본값은 'all'로 설정
const category = params.category || 'all';
return(
<>
<Categories />
<NewsList category={category} />
</>
)
}
export default NewsPage;
URL 파라미터에 따라 카테고리가 선택되어 있지 않다면 'all', 선택되어 있다면 해당 카테고리 값을 props로 넘겨주었습니다.
App.js
import { Route, Routes } from '../node_modules/react-router-dom/dist/index';
import NewsPage from './pages/NewsPage';
function App() {
//const [category, setCategory] = useState('all');
//const onSelect = useCallback(category => setCategory(category), []);
return (
<Routes>
<Route path='/' element={<NewsPage />} />
<Route path='/:category' element={<NewsPage />} />
</Routes>
)
}
export default App;
NewsPage에서 URL에 따른 카테고리 값을 넘겨주므로, App에선 현재 카테고리 값과 onSelect 함수를 넘겨줄 필요가 없습니다.
라우트를 정의해준 것을 확인할 수 있습니다.
Categories에서 NavLink 사용하기
카테고리 선택 및 선택 카테고리에 대해 스타일을 부여해주는 기능을 NavLink로 대체해 보겠습니다.
특정 컴포넌트에 styled-components를 사용할 때는 styled(컴포넌트이름)``의 형식을 사용합니다.
Categories.js
import styled, { css } from "styled-components";
import { NavLink } from "../../node_modules/react-router-dom/dist/index";
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 = ({ category, onSelect }) => {
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;
NavLink로 만들어진 Category 컴포넌트의 to값은 '/카테고리이름'으로 설정해주었습니다.
다만, 전체보기인 경우에는 '/all' 대신 '/'로 설정해주었습니다. 잘 렌더링 되는 것을 확인할 수 있습니다.
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 react-hooks/exhaustive-deps */
}, deps);
return [loading, resolved, error];
}
usePromise 커스텀 Hook은 Promise의 대기 중, 완료 결과, 실패 결과에 대한 상태를 관리합니다.
usePromise의 의존 배열 deps를 파라미터로 받아오고 이를 useEffect의 의존 배열로 설정합니다.
(이 때 ESLink 경고가 나타나게 되고 해당 오류의 커서를 가져다댄 후 빠른 수정...문구를 클릭하면 해당 규칙을 무시하는 주석을 입력할 수 있음)
NewsList.js
import { useState, useEffect } 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, resolved, error] = usePromise(() => {
const query = category === 'all' ? '' : `&category=${category}`;
return axios.get(`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=460a690efc5748c7979f8762eea705fe`);
}, [category]);
// 대기 중일 때
if(loading) {
return <NewsListBlock>대기 중...</NewsListBlock>
}
// 아직 resolved 값이 설정되지 않았을 때
if(!resolved) {
return null;
}
// 에러 발생 시
if(error) {
return <NewsListBlock>에러 발생!</NewsListBlock>
}
// resolved값이 유효하고 데이터를 가져온 후 (loading = false)
const { articles } = resolved.data;
return (
<NewsListBlock>
{articles.map(article => (
<NewsItem key={article.url} article={article} />
))}
</NewsListBlock>
)
}
export default NewsList;
usePromise에 API 값을 가져와 리턴하는 함수 자체를 파라미터로 넘겨주었습니다.
이를 실행 한 후 그에 따른 작업을 수행하여 값을 return해주고 이를 받아 사용하는 것을 확인할 수 있습니다.
'JavaScript > React' 카테고리의 다른 글
React) Context API - useContext, static contextType 사용하기 (0) | 2022.11.28 |
---|---|
React) Context API - 사용 이유, createContext, Consumer, Provider, 동적 Context 사용하기 (0) | 2022.11.28 |
React) 외부 API를 연동하여 뉴스 뷰어 만들기 ① - API 키 발급, 데이터 연동 (0) | 2022.11.24 |
React) 비동기 작업의 이해 - 콜백함수, Promise, async/await, axios (0) | 2022.11.24 |
React) 리액트 라우터로 SPA 개발하기 - useNavigate, NavLink, NotFound, Navigate) (0) | 2022.11.23 |