2020. 2. 24. 16:44

 

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
Posted by yongminLEE