- 뉴스 API (newsapi.org)의 데이터를 불러와서 화면에 렌더링.
- 카테고리별로 분류하여 조회 가능.
- 클릭시 해당 기사로 넘어감.
react
javaScript
├── public
└── src
├── lib
│ └── usePromise
├── components
│ ├── pages
│ │ └── NewsPage
│ ├── Categories
│ ├── NewsItem
│ └── NewsList
├── App
└── index
create-react-app
"dependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"axios": "^0.27.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"styled-components": "^5.3.5",
"web-vitals": "^2.1.0"
},
$ git clone https://github.com/thisisyjin/news-viewer.git
$ npm install
$ npm start || yarn start
내 개발 블로그 에 따로 포스팅해두었다.
- HTTP 요청을 Promise 기반으로 처리.
const App = () => {
const [data, setData] = useState(null);
const onClickBtn = () => {
axios.get('https://jsonplaceholder.typicode.com/todos/1').then(response => {
setData(response.data);
});
};
...
axios.get(url)
로 데이터를 가져온다.response.data
를 받아온다.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);
}
};
async () =>
와 같이 나타냄.response
라는 변수에 저장한 후,state
)를 response.data로 변경함.
await
은 Promise를 기다리기 위해 사용된다.- await은 async function 내부에서만 사용할 수 있다.
try {요청} catch (e) {에러시}
로 구성된다.#root
에 render()articles
와 loading
이 있음.async
함수를 이용하여 API를 불러옴.response = await axios.get(url)
articles
state에 데이터를 저장함.if(loading) 이면 '대기중'을 렌더링하고,
if(!articles) 면 아래 return에서 map함수에서 오류가 나지 않도록 필터링 해준다.
articles(state
)는 배열이므로, 배열 메서드인 map을 이용하여 각각의 요소들을
NewsItem 컴포넌트로 렌더링해줌.
NewsItem 컴포넌트에 article이라는 Props를 넘겨줌. (각각의 요소 자신)
props
를 전달받음.
NavLink
는 react-router의 내장 컴포넌트 중 하나로,
현재 라우터와 일치하면 스타일 or 클래스를 부여하는 컴포넌트이다.
참고 - NavLink의
isActive
// 1️⃣ 클래스 적용시 className={isActive => "nav-link" + (!isActive ? " unselected" : "") } // 또는 activeClassName="selected" // 2️⃣ 스타일 적용시 style={isActive => ({ color: isActive ? "green" : "blue" }) }
Category 컴포넌트가 active 라는 클래스를 가지면,
&.active {
font-weight: 600;
border-bottom: 2px solid #22b8cf;
color: #22b8cf;
&:hover {
color: #3bc9db;
}
}
위처럼 클래스 선택자에 의해 스타일이 적용된다.
to={c.name === 'all' ? '/' : `/${c.name}`
또한, to 프로퍼티에는 c.name이 'all'이라면 경로는 /가 되게 하고,
아니라면 /c.name이 되도록 설정한다.
useParams
사용.const params = useParams();
const category = params.category || 'all';
<></>
const query = category === 'all' ? '' : `&category=${category}`;
NewsPage로부터 props로 받은 category로 query를 결정함.
category가 'all'이라면 ''로, 아니라면 category로 결정.
-> 참고로, category는 useParams로 받아온 params의 값이다.
(params=''인 경우에는 category='all'로 기본값.)
await axios.get(url)에서 url 부분을 수정해준다.
-> ES6 태그드 템플릿 리터럴로 작성함.
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=(인증키)`
country=kr 뒤부분에 바로 query를 넣어줌.
-> 이 url은 동적으로 바뀌는 url이 됨.
1) params 값에 따라서 -> 2) category 값이 설정 -> props로 넘겨줌
-> 3) query값이 설정 -> 4) axios.get을 요청할 url이 동적 설정.
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];
}
state
는 loading, resolved, error 총 세가지를 가짐.
-> usePromise 함수가 return 하는 값은 모두 state들임.
process 라는 함수를 useEffect 내부에서 정의함.
-> async 함수이므로 선언한 후 사용해야 함.
try(fetch부분) catch(에러시) 구조로 이루어지며,
try-catch문 이전에는 setLoading(true)
try-catch문 이후에는 setLoading(false)를 해줌.
resolved = await promiseCreator()을 해줌.
여기서 promiseCreator
는 usePromise의 첫번째 인자로 받는 함수이다.
-> axios.get(url)을 하면 된다.
const [loading, response, error] = usePromise(() => {
const query = category === 'all' ? '' : `${category}`;
return axios.get(
`https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=(인증키)`
);
}, [category]);
resolved
는 state가 아닌 변수이다.