본문으로 바로가기

14-1. axios


axios는 자바스크립트 HTTP 클라이언트이며 HTTP 요청을 promise기반으로 처리한다

14-1-1. promise


const App = () => {
  const [data,setData] = useState(null);

  const onClick = () => {
    axios.get('https://jsonplaceholder.typicode.com/todos/1').then(res=>{
      setData(res.data);
    })
  };

  return(
      <div>
        <button onClick={onClick}>load</button>
        <div>
          {data&&<textarea rows={7} value={JSON.stringify(data,null,2)} readOnly/> }
        </div>
      </div>
  );
}

이 상태로 load를 누르면 data에 응답을 받아 컴포넌트에 출력한다

14-1-2. async/await


async/await를 사용할 경우에는 아래와 같다

// in App.js
const onClick = async () => {
    try{
      setData(await axios.get('https://jsonplaceholder.typicode.com/todos/1'));
    }catch (e){
      console.log(e);
    }
  };

14-2. 뉴스 뷰어 만들기


to-do 애플리케이션을 만들 때와 마찬가지로 만들면서 헤맸던 부분만 기록하려고 한다

14-2-1. 데이터 연동


컴포넌트가 렌더링되기 전에 데이터를 받아와야하기 때문에 useEffect를 사용해서 받아와야한다

NewsList컴포넌트에서 articles를 선언한 후에 useEffect에서 async와 await를 이용해서 데이터를 받아오려고 했다

const NewsList = () => {
    const [articles, setArticles] = useState(null);
    useEffect( async()=>{
                const data = await axios.get('url');
                setArticles(data);
    });
        ...
}

이런식으로 받아오려 했으나 이러면 안된다는 걸 배웠다..

useEffect의 리턴값은 뒷 정리 함수 즉, 컴포넌트가 언마운트되기 전에 실행될 함수가 반환되어야하기 때문에 지금 처럼 콜백을 등록할 경우 promise값이 들어가게된다

useEffect(()=>{
        const fetchData = async ()=>{
            try {
                const {data} = await axios.get(`url`);
                setArticles(data.articles);
            } catch (e){
                console.log(e);
            }
        }
        fetchData();
    },[]);

이런식으로 콜백 함수 내부에 async함수를 또 생성한 뒤에 useEffect 내부에서 다시 실행시켜줘야한다

두 번째 파라미터로 빈 배열을주어서 처음 렌더링될 때만 실행되게끔 해주었다

연동시 주의점


책에서는 loading이라는 상태값을 추가해서 API 요청이 대기 중인 경우 다른 화면을 표시하게끔 해주고 아직 데이터를 불러오기 전인 경우 null값을 주고있다

const NewsList = () => {
    const [articles, setArticles] = useState(null);
    const [loading, setLoading] = useState(false);
    useEffect(()=>{
        const fetchData = async ()=>{
            setLoading(true);
            try {
                const {data} = await axios.get(`url`);
                setArticles(data.articles);
            } catch (e){
                console.log(e);
            }
            setLoading(false);
        }
        fetchData();
    },[]);

    if(loading) {
        return <NewsListBlock>Waiting for loading...</NewsListBlock>
    }

    if(!articles){
        return null;
    }

    return (
        <NewsListBlock>
            {articles.map(v=><NewsItem key={v.url} article={v}/>)}
        </NewsListBlock>
    );
};

데이터를 받아오기 전까지 loading의 상태는 true이기 때문에 사용자가 기다리는 중에는 Waiting...이라는 문구가 뜨게되고

articles에 값이 할당되기 전에 map으로 접근해서 jsx를 만들려고 하면 articles는 배열이 아닌 null인 상태이기 때문에 에러가 나기 때문에 할당되기 전에는 null자체를 리턴해준다

14-2-2. 라우터 적용하기


여긴 적지 않았지만 책에서 나오는 예제에서는 category State를 만들어서 카테고리 메뉴들을 클릭하면 카테고리의 값이 바뀌고 바뀐 카테고리 값을 이용해서 api에게 해당하는 카테고리의 데이터만 가져왔다

이제는 살짝 다르게 카테고리를 클릭하면 주소가 바뀌고 바뀐 주소의 url를 라우터를 사용해 api에 요청해보자

우선 라우터를 사용하려면 BrowserRouter로 감싸주어야한다

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

그 뒤 App은 라우트를 컴포넌트하고 categories 부분을 받는다

const App = () => {

  return(
      <Route path={'/:categories?'} component={NewsPage}/>
  );
}

/:categories?


/:categories? 뒤에 붙은 물음표는 값이 없을 수도 있다는 것을 나타낸다

?를 붙여주지 않으면 기본 주소인 '/'로 들어가게되면 라우트가 NewsPage로 라우팅을 해주지 않는다

그렇게 받아온 url은 match의 params에 저장된다는 것을 배웠다

const NewsPage = ({match}) => {
    const category = match.params.categories || 'general';
    return (
        <div>
            <Categories/>
            <NewsList category={category}/>
        </div>
    );
};

받아온 데이터를 카테고리에 저장해주고 NewsList의 props로 넘겨주는데

이때 우리는 '/'로 들어온 주소도 NewsPage를 라우팅하게끔 설정해주어서 categories의 값이 없을 경우 기본 설정인 general을 할당해주었다

// in Categories.js

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 = () => {
    return (
        <CategoriesBlock>
            {categories.map(v=><Category to={`/${v.name}`} key={v.name}>{v.text}</Category>)}
        </CategoriesBlock>
    );
};

styled(NavLink)


styled.div~~, styled.button~~ 같은 경우는 해당 태그에 css스타일을 적용하는데

HTML요소의 태그가 아닌 다른 컴포넌트에 적용하고싶을 때는 (컴포넌트명)으로 해주면된다

그렇게 되면 NavLink의 컴포넌트 속성과 ~~스타일을 가진 Category라는 컴포넌트가 생성되는 것

Link, NavLink


이 둘을 사용할 때 props로 to 값을 주지않으면 에러가 나게된다는 점을 기억하자

14-2-3. 커스텀 Hook


NewsList에서 비동기로 API를 요청하는 부분은 프로젝트에서 다양한 곳에서도 사용될 수 있다

그런 부분은 모듈화 후 재사용하여 중복되는 코드를 방지하고 생산성을 높일 수 있는데 한 번 해보도록 하자

const NewsList = ({category}) => {
    const [loading, response, error] = usePromise(()=>{
        return axios.get(`http://newsapi.org/v2/top-headlines?country=kr&category=${category}&apiKey=c5e53e4a862f42e19936a95c3b7bf9d6`);
    },[category]);

    if(loading) {
        return <NewsListBlock>Waiting for loading...</NewsListBlock>
    }
    if(!response) {
        return null;
    }
    if(error){
        return <NewsListBlock>Error!</NewsListBlock>
    }
    const {articles} = response.data;

    return (
        <NewsListBlock>
            {articles.map(v=><NewsItem key={v.url} article={v}/>)}
        </NewsListBlock>
    );
};

코드가 전체적으로 바뀌었는데

usePromise라는 커스텀 훅을 사용해서 비동기 통신 함수와, 의존성 배열을 파라미터로 넘겨주고 데이터를 받는 동안 상태를 알려주는 loading, 성공시에 데이터를 받아오는 response, 실패시에 에러를 받아오는 error 변수들을 받아온다

이제 usePromise를 만들어보자

const 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();
    },deps);
    return [loading,resolved,error];
}

함수는 어려운게 없다 초기에 작성한 데이터 연동 코드를 파라미터화 해서 재사용이 가능하게 만들었을 뿐이다

'React > 리액트를 다루는 기술' 카테고리의 다른 글

16. 리덕스 라이브러리 이해하기  (0) 2021.02.11
15. Context API  (0) 2021.02.10
13. 리액트 라우터로 SPA 개발하기  (0) 2021.02.06
12. immer  (0) 2021.02.05
11. 컴포넌트 성능 최적화  (0) 2021.02.01