1. Asynchrony ?
2. axios
3. 예제
1. Asynchrony ?
- 정의 : 비동기작업은 이전 작업이 끝날때 까지 기다릴 필요 없이 다른 작업을 진행하는 것.
- Ajax (Asynchronous Javascript And Xml)
: 브라우저가 가지고있는 XMLHttpRequest 객체를 이용해서 전체 페이지를 새로 고치지 않고도 페이지의 일부만을 위한 데이터를 로드하는 기법
- callback : 특정 로직이 끝났을때 실핼항 동작을 정의하고 있는 함수
- promise : 어떤 작업이 성공했을 때(resolve), promise 객체의 then() 함수에 넘겨진 파라미터(함수)를 단 한번만 호출하겠다는 약속. callback 의 경우 제어권이 호출되는 함수로 넘어가 버리기 때문에 신뢰성이 다소 떨어지지만 promise 는 함수 실행이 성공했을때 then() 함수의 파라미터(함수)가 단 한번만 호출되기 때문에 함수를 호출하는 입장에서 확신을 가지고 코드를 작성할 수 있습니다. 또한 실패했을 경우(reject)에도 catch()함수를 통해서 실패 이후의 작업을 처리할 수 있습니다.
- 대기(pending): 이행하거나 거부되지 않은 초기 상태.
- 이행(fulfilled): 연산이 성공적으로 완료됨.
- 거부(rejected): 연산이 실패함.
- async/await : promis를 더욱 쉽게 사용할 수 있도록 해주는 ES8문법. function 키워드 앞에 async만 붙여주면 되고 비동기로 처리되는 부분 앞에 await만 붙여주면 됩니다. 다만, 몇 가지 주의할 점이 있다면 await 뒷부분이 반드시 promise 를 반환해야 한다는 것과 async function 자체도 promise 를 반환한다는 것입니다.
2. axios
- 정의 : 리액트 애플리케이션에서 ajax를 더욱 쉽게 구현하기 위한 promis기반 http통신 라이브러리
- axios 예제1 (async/await 미적용)
//axios 예제1 (async/await 미적용)
import React, { useState } from 'react';
import axios from 'axios';
import './App.css';
const App = () => {
const [data, setDate] = useState(null);
const handleClick = () => {
axios.get('https://jsonplaceholder.typicode.com/todos/1').then(response => {
setDate(response.data);
});
};
return (
<>
<div>
<button onClick={handleClick}>불러오기</button>
</div>
{data && (
<textarea
row={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</>
);
};
export default App;
- axios 예제2 (async/await 적용)
//axios 예제2 (async/await 적용)
import React, { useState } from 'react';
import axios from 'axios';
import './App.css';
const App = () => {
const [data, setDate] = useState(null);
const handleClick = async () => {
try {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/todos/1'
);
setDate(response.data);
} catch (e) {
console.log(e);
}
};
return (
<>
<div>
<button onClick={handleClick}>불러오기</button>
</div>
{data && (
<textarea
row={7}
value={JSON.stringify(data, null, 2)}
readOnly={true}
/>
)}
</>
);
};
export default App;
3. 예제 : NewsViewer 프로젝트
3-1. UI 구성
// newsviewer/src/components/NewsItem.js
import React from "react";
import styled from "styled-components";
const StyledNewsItem = 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 (
<StyledNewsItem>
{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>
</StyledNewsItem>
);
};
export default NewsItem;
// newsviewer/src/components/NewsList.js
import React from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
const StyledNewsList = 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 (
<StyledNewsList>
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
<NewsItem article={sampleArticle} />
</StyledNewsList>
);
};
export default NewsList;
//newsviewer/src/App.js
import React from "react";
import NewsList from "./components/NewsList";
import "./App.css";
function App() {
return (
<>
<NewsList />
</>
);
}
export default App;
3-2. 데이터 연동
- useEffect를 이용하여 컴포넌트가 처음 렌더링되는 시점에 API를 요청.
- useEffect에서 반환하는 값은 cleanup함수 이므로 async를 붙이면 안된다. 따라서, useEffect 내부에서 async/await 사용시 함수 내부에서 async 키워드가 붙은 새로운 함수를 정의하여 사용
// newsviewer/src/components/NewsList.js
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
import axios from "axios";
const StyledNewsList = 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(null);
const API_KEY = "-----------------------------";
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(
`http://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=${API_KEY}`
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <StyledNewsList>loading...</StyledNewsList>;
}
if (!articles) {
return null;
}
return (
<StyledNewsList>
{articles.map(article => (
<NewsItem key={article.url} article={article} />
))}
</StyledNewsList>
);
};
export default NewsList;
3-3. 카테고리 기능 구현
// newsviewer/src/components/Categories.js
import React from "react";
import styled, { css } from "styled-components";
const categories = [
{
name: "all",
text: "all"
},
{
name: "business",
text: "business"
},
{
name: "entertainment",
text: "entertainment"
},
{
name: "health",
text: "health"
},
{
name: "science",
text: "science"
},
{
name: "technology",
text: "technology"
}
];
const StyledCategories = styled.div`
display: flex;
padding: 1rem;
width: 768px;
margin: 0 auto;
@media scree and (max-width: 768px) {
width: 100%;
overflow-x: auto;
}
`;
const StyledCatgory = styled.div`
font-size: 1.2rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.3rem;
&:hover {
color: rgba(128, 128, 128, 0.3);
}
${props =>
props.active &&
css`
font-size: 600;
border-bottom: 2px solid red;
color: red;
&:hover {
color: white;
background: red;
}
`}
& + & {
margin-left: 1rem;
}
`;
const Categories = ({ category, handleSelect }) => {
return (
<StyledCategories>
{categories.map(c => (
<StyledCatgory
key={c.name}
active={category === c.name}
onClick={() => handleSelect(c.name)}
>
{c.text}
</StyledCatgory>
))}
</StyledCategories>
);
};
export default Categories;
// newsviewer/src/App.js
import React, { useState, useCallback } from "react";
import NewsList from "./components/NewsList";
import Categories from "./components/Categories";
import "./App.css";
function App() {
const [category, setCategory] = useState("all");
const handleSelect = useCallback(category => setCategory(category), []);
return (
<>
<Categories category={category} handleSelect={handleSelect} />
<NewsList category={category} />
</>
);
}
export default App;
// newsviewer/src/components/NewsList.js
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import NewsItem from "./NewsItem";
import axios from "axios";
const StyledNewsList = 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 [articles, setArticles] = useState(null);
const [loading, setLoading] = useState(null);
const API_KEY = "-------------------------------";
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const selectedCategory = category === "all" ? "" : `&category=${category}`;
const response = await axios.get(
`http://newsapi.org/v2/top-headlines?country=us${selectedCategory}&apiKey=${API_KEY}`
);
setArticles(response.data.articles);
} catch (e) {
console.log(e);
}
setLoading(false);
};
fetchData();
}, [category]);
if (loading) {
return <StyledNewsList>loading...</StyledNewsList>;
}
if (!articles) {
return null;
}
return (
<StyledNewsList>
{articles.map(article => (
<NewsItem key={article.url} article={article} />
))}
</StyledNewsList>
);
};
export default NewsList;
3-4. react-router 적용
// newsviewer/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;
// newsviewer/src/App.js
import React from 'react';
import NewsPage from './pages/NewsPage';
import { Route } from 'react-router-dom';
import './App.css';
function App() {
return (
<>
<Route path="/:category?" component={NewsPage} />
</>
);
}
export default App;
// newsviewer/src/components/Categories.js
import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
const categories = [
{
name: 'all',
text: 'all'
},
{
name: 'business',
text: 'business'
},
{
name: 'entertainment',
text: 'entertainment'
},
{
name: 'health',
text: 'health'
},
{
name: 'science',
text: 'science'
},
{
name: 'technology',
text: 'technology'
}
];
const StyledCategories = styled.div`
display: flex;
padding: 1rem;
width: 768px;
margin: 0 auto;
@media scree and (max-width: 768px) {
width: 100%;
overflow-x: auto;
}
`;
const StyledCatgory = styled(NavLink)`
font-size: 1.2rem;
cursor: pointer;
white-space: pre;
text-decoration: none;
color: inherit;
padding-bottom: 0.3rem;
&:hover {
color: rgba(128, 128, 128, 0.3);
}
&.active {
font-size: 600;
border-bottom: 2px solid red;
color: red;
&:hover {
color: white;
background: red;
}
}
& + & {
margin-left: 1rem;
}
`;
const Categories = () => {
return (
<StyledCategories>
{categories.map(c => (
<StyledCatgory
key={c.name}
activeClassName="active"
exact={c.name === 'all'}
to={c.name === 'all' ? '/' : `/${c.name}`}
>
{c.text}
</StyledCatgory>
))}
</StyledCategories>
);
};
export default Categories;
'Javascript > React' 카테고리의 다른 글
#10 SPA 와 Routing (0) | 2020.02.24 |
---|---|
#9 immer를 사용한 불변성(Immutability) 유지 (0) | 2020.02.24 |
#8-1 컴포넌트 최적화 (0) | 2020.02.17 |
#8 React todo-app 1 (0) | 2020.02.17 |
#7 Component Styling (0) | 2020.02.17 |