React Router로 페이지 이동 관리하기, 동적 라우팅과 URL 파라미터 처리

리액트는 사용자 인터페이스를 구축하기 위한 강력한 라이브러리입니다. 그중에서도 React Router는 리액트 애플리케이션에서 클라이언트 측 라우팅을 관리할 수 있는 훌륭한 도구입니다. 이를 통해 사용자가 서로 다른 URL을 통해 다양한 페이지를 탐색할 수 있도록 도와줍니다. 이 글에서는 React Router의 기본 개념과 동적 라우팅 및 URL 파라미터 처리 방법에 대해 자세히 살펴보겠습니다.

1. React Router 소개

React Router는 리액트 애플리케이션에 “페이지”를 추가하는 데 필요한 도구를 제공합니다. 기본적으로 React Router는 URL과 그에 따라서 렌더링할 컴포넌트를 연결합니다. 이를 통해 SPA(싱글 페이지 애플리케이션) 내에서도 각기 다른 URL을 사용할 수 있으며, 브라우저의 주소 표시줄을 통해 페이지를 전환할 수 있습니다.

2. React Router 설치하기

리액트 애플리케이션에 React Router를 추가하려면 다음 명령어로 패키지를 설치할 수 있습니다:

npm install react-router-dom

설치가 완료되면, 기본적인 라우팅을 설정할 준비가 끝났습니다.

3. 기본적인 라우팅 설정

React Router를 사용하기 위해 가장 먼저 해야 할 일은 BrowserRouterRoute 컴포넌트를 사용하는 것입니다. 기본적인 예제는 다음과 같습니다:


import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = () => <h2>Home Page</h2>;
const About = () => <h2>About Page</h2>;

const App = () => {
  return (
    <Router>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </div>
    </Router>
  );
};

export default App;

위의 코드에서 Router 컴포넌트는 애플리케이션의 루트 컴포넌트로, 그 내부에 여러 개의 Route 컴포넌트를 포함하고 있습니다. Switch 컴포넌트는 자식으로 받은 Route 중 첫 번째로 일치하는 것을 렌더링합니다. exact 속성을 추가하면 ‘/’ 경로가 정확히 맞아떨어질 때만 Home 컴포넌트를 렌더링합니다.

4. 동적 라우팅

이제 동적 라우팅을 구현해 보겠습니다. 동적 라우팅이란, URL의 일부가 동적으로 변할 수 있는 라우팅을 생성하는 방법입니다. 예를 들어, 블로그 포스트의 디테일 페이지를 구현할 때 특정 포스트의 ID를 URL의 일부로 사용하여 동적으로 해당 포스트를 로드할 수 있습니다.


const Post = ({ match }) => {
  return <h2>Post ID: {match.params.id}</h2>;
};

const App = () => {
  return (
    <Router>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/post/:id" component={Post} />
        </Switch>
      </div>
    </Router>
  );
};

위의 예제에서 Post 컴포넌트는 URL의 파라미터를 사용하여 포스트 ID를 가져옵니다. match.params.id를 통해 URL에서 동적으로 변하는 ID 값을 얻을 수 있습니다.

5. URL 파라미터 처리 방법

React Router를 사용하면서 중요한 것은 URL 파라미터를 처리하는 것입니다. URL 파라미터는 URL 경로의 일부로서, 특정 데이터를 동적으로 처리하기 위해 사용됩니다. 위에서 설명한 Post 컴포넌트는 URL 파라미터를 처리하는 좋은 예입니다.

여기서 URL 파라미터를 사용하여 해당 포스트의 내용을 가져오는 방법을 설명합니다. 예를 들어, 사용자가 포스트 개별 URL을 클릭했을 때, 서버에서 해당 포스트의 데이터를 가져오는 방법은 다음과 같습니다:


import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Post = ({ match }) => {
  const [post, setPost] = useState(null);
  const postId = match.params.id;

  useEffect(() => {
    const fetchPost = async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
      const data = await response.json();
      setPost(data);
    };
    
    fetchPost();
  }, [postId]);

  if (!post) return <p>Loading...</p>;

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
};

const App = () => {
  return (
    <Router>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/post/:id" component={Post} />
        </Switch>
      </div>
    </Router>
  );
};

위의 예제에서는 useEffect hook을 사용하여 컴포넌트가 마운트될 때 API 요청을 보내고, 특정 포스트의 데이터를 가져옵니다. 가져온 후에는 상태를 업데이트하여 화면에 포스트 제목과 내용을 보여줍니다.

6. URL 쿼리 파라미터 처리하기

동적 라우팅 외에도 쿼리 파라미터를 통해 URL에 추가적인 데이터를 전달할 수 있습니다. 쿼리 파라미터는 URL의 ‘?’ 이후에 위치하며, 여러 개의 파라미터를 ‘&’로 구분합니다. 이를 React Router에서 처리하는 방법은 다음과 같습니다:

예를 들어, 사용자가 검색 결과를 확인할 수 있는 페이지를 만들고 싶다면, URL은 다음과 같은 형태가 될 수 있습니다:

http://example.com/search?query=react&page=2

이러한 쿼리 파라미터를 React Router의 useLocation hook을 사용하여 가져올 수 있습니다:


import React from 'react';
import { BrowserRouter as Router, Route, Switch, useLocation } from 'react-router-dom';

const useQuery = () => {
  return new URLSearchParams(useLocation().search);
};

const Search = () => {
  let query = useQuery();
  let searchTerm = query.get('query');
  let page = query.get('page');

  return (
    <div>
      <h2>Search Results for: {searchTerm}</h2>
      <p>Page: {page}</p>
    </div>
  );
};

const App = () => {
  return (
    <Router>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/search" component={Search} />
        </Switch>
      </div>
    </Router>
  );
};

이 예제에서 useQuery라는 커스텀 훅을 만들어 URL 쿼리 파라미터를 쉽게 가져올 수 있게 했습니다. searchTermpage 변수에 쿼리 파라미터의 값을 할당하여 화면에 표시합니다.

7. Redirect와 Navigate 사용하기

사용자가 특정 조건을 만족하지 않을 때 다른 페이지로 리디렉션할 필요가 있을 수 있습니다. React Router에서는 Redirect 컴포넌트를 사용하여 이 작업을 수행할 수 있습니다. 아래는 사용자가 로그인된 상태가 아닐 때 로그인 페이지로 리다이렉트하는 예시입니다:


import React from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';

const ProtectedRoute = ({ component: Component, isAuthenticated, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props =>
        isAuthenticated ? ( <Component {...props} /> ) : ( <Redirect to="/login" /> )
      }
    />
  );
};

const App = () => {
  const isAuthenticated = false; // 예시를 위한 값입니다.

  return (
    <Router>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <ProtectedRoute path="/protected" component={Protected} isAuthenticated={isAuthenticated} />
          <Route path="/login" component={Login} />
        </Switch>
      </div>
    </Router>
  );
};

위의 예제에서 ProtectedRoute 컴포넌트는 인증 여부에 따라 사용자를 원하는 페이지에 리다이렉트합니다. 사용자가 인증되지 않았다면 /login 페이지로 이동하게 됩니다.

8. BrowserRouter와 HashRouter

React Router는 다양한 라우터를 제공합니다. 여기서는 BrowserRouterHashRouter에 대해 설명합니다. BrowserRouter는 HTML5의 history API를 사용하여 URL을 관리하며, 가장 일반적으로 사용됩니다. 반면에 HashRouter는 해시(#)를 사용하여 URL 구조를 관리합니다. 두 라우터의 차이는 주로 서버 설정에 관련되어 있습니다.

일반적으로 서버에서 모든 경로를 처리해야 할 경우 BrowserRouter를 사용합니다. 그러나 서버 설정이 복잡한 경우, HashRouter를 사용하는 것이 편리할 수 있습니다.


import React from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';

const App = () => {
  return (
    <HashRouter>
      <div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
      </div>
    </HashRouter>
  );
};

해시 라우터를 사용하면 URL이 다음과 같이 표시됩니다:

http://example.com/#/about

9. 결론

React Router를 활용하면 리액트 애플리케이션에서 페이지 간 네비게이션을 쉽게 관리할 수 있습니다. 동적 라우팅과 URL 파라미터 처리 등 다양한 기능을 통해 더욱 강력하고 유연한 애플리케이션을 구축할 수 있습니다. 지금까지 소개한 내용을 통해 React 애플리케이션에서 라우팅을 설정하고, 동적 데이터 처리 및 URL 파라미터를 사용하는 방법에 대해 이해했기를 바랍니다.

앞으로도 다양한 기능을 추가하고, 더 나은 사용자 경험을 제공하는 방향으로 애플리케이션을 발전시켜 나가시길 바랍니다. happy coding!

커스텀 마커 및 정보창 만들기, 정보창에 장소 정보 및 이미지, 링크 추가하기

웹 애플리케이션에서 사용자에게 더 많은 정보를 제공하고, 경험을 향상시키기 위해 맵(Mapping) 기능은 아주 중요합니다. 이 강좌에서는 리액트(React)와 구글 맵 API를 사용하여 커스텀 마커 및 정보창을 만들고, 해당 정보창에 장소 정보, 이미지 및 링크를 추가하는 방법에 대해 알아보겠습니다.

1. 환경 세팅하기

시작하기 전에 프로젝트 환경을 설정해야 합니다. Create React App을 사용하여 새로운 리액트 프로젝트를 생성합니다.

npx create-react-app custom-marker-example
cd custom-marker-example
npm install @react-google-maps/api

2. 구글 맵 API 키 얻기

구글 맵을 사용하기 위해서는 API 키가 필요합니다. 구글 클라우드 플랫폼에서 새로운 프로젝트를 생성하고, ‘Maps JavaScript API’를 활성화한 후 API 키를 생성하세요.

3. 기본 맵 컴포넌트 만들기

이제 기본적인 맵 컴포넌트를 만들어보겠습니다. App.js 파일을 열고 다음 코드를 추가합니다.

import React from 'react';
import { GoogleMap, LoadScript } from '@react-google-maps/api';

const containerStyle = {
  width: '800px',
  height: '400px'
};

const center = {
  lat: -3.745,
  lng: -38.523
};

function MyMapComponent() {
  return (
    
      
        {/* 여기에 마커를 추가할 것입니다 */}
      
    
  );
}

export default MyMapComponent;

4. 커스텀 마커 추가하기

구글 맵에 커스텀 마커를 추가하려면, 다음과 같이 컴포넌트를 수정합니다. 마커의 위치와 이미지를 설정하여 사용자에게 명확한 정보를 제공합니다.

import React from 'react';
import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api';

const containerStyle = {
  width: '800px',
  height: '400px'
};

const center = {
  lat: -3.745,
  lng: -38.523
};

const markerPosition = {
  lat: -3.745,
  lng: -38.523
};

function MyMapComponent() {
  return (
    
      
         {
            alert('Marker clicked');
          }}
        />
      
    
  );
}

export default MyMapComponent;

5. 정보창(InfoWindow) 추가하기

정보창은 사용자에게 추가 정보를 제공할 수 있는 유용한 도구입니다. 마커와 함께 정보창을 추가하여 장소에 대한 설명, 이미지 및 링크를 표시할 수 있습니다.

import React, { useState } from 'react';
import { GoogleMap, LoadScript, Marker, InfoWindow } from '@react-google-maps/api';

const containerStyle = {
  width: '800px',
  height: '400px'
};

const center = {
  lat: -3.745,
  lng: -38.523
};

const markerPosition = {
  lat: -3.745,
  lng: -38.523
};

function MyMapComponent() {
  const [selected, setSelected] = useState(null);

  return (
    
      
         {
            setSelected(markerPosition);
          }}
        />

        {selected ? (
           {
              setSelected(null);
            }}
          >
            

장소 제목

여기에는 장소에 대한 간단한 설명을 추가합니다.

장소 이미지 자세한 정보 보기
) : null}
); } export default MyMapComponent;

6. 다수의 마커 및 정보창 추가하기

이제 여러 마커와 정보창을 추가하는 방법을 살펴보겠습니다. 예를 들어, 장소 목록을 배열로 저장하고 맵에서 동적으로 렌더링할 수 있습니다.

import React, { useState } from 'react';
import { GoogleMap, LoadScript, Marker, InfoWindow } from '@react-google-maps/api';

const containerStyle = {
  width: '800px',
  height: '400px'
};

const center = {
  lat: -3.745,
  lng: -38.523
};

const locations = [
  {
    id: 1,
    title: "장소 1",
    position: { lat: -3.745, lng: -38.523 },
    description: "장소 1 설명",
    imageUrl: "http://example.com/image1.jpg",
    link: "http://example.com/location1"
  },
  {
    id: 2,
    title: "장소 2",
    position: { lat: -3.755, lng: -38.513 },
    description: "장소 2 설명",
    imageUrl: "http://example.com/image2.jpg",
    link: "http://example.com/location2"
  },
  // 추가 장소를 여기에 더할 수 있습니다
];

function MyMapComponent() {
  const [selected, setSelected] = useState(null);

  return (
    
      
        {locations.map(location => (
           {
              setSelected(location);
            }}
          />
        ))}

        {selected ? (
           {
              setSelected(null);
            }}
          >
            

{selected.title}

{selected.description}

장소 이미지 자세한 정보 보기
) : null}
); } export default MyMapComponent;

7. 스타일 및 최적화

사용자 경험을 위한 스타일은 매우 중요합니다. CSS를 사용하여 마커와 정보창의 디자인을 개선할 수 있습니다. 또한, 마커나 정보창을 동적으로 조절하여 성능을 최적화할 수 있습니다. 예를 들어, Lazy loading 또는 코드 스플리팅을 고려하여 사용자에게 더 나은 성능을 제공할 수 있습니다.

8. 결론

이번 강좌에서는 리액트에서 구글 맵을 활용하여 커스텀 마커 및 정보창을 만드는 방법에 대해 배웠습니다. 장소에 대한 정보, 이미지 및 링크를 포함하는 방법을 소개했으며, 다수의 마커를 관리하는 방법도 설명했습니다. 이 기술은 다양한 웹 애플리케이션에 적용할 수 있으며, 사용자에게 풍부한 경험을 제공하는 데 큰 도움이 될 것입니다. 더 나아가 주어진 데이터를 바탕으로 다양한 마커와 정보창을 자유롭게 구성하여 사용자의 필요에 맞춘 웹 애플리케이션을 제작해 보세요.

9. 참고 자료

고급 상태 관리 Redux 사용하기, Redux의 기본 개념과 상태 흐름

프론트엔드 개발에서 상태 관리는 애플리케이션의 성능과 직결되며, 특히 리액트와 같은 UI 라이브러리를 사용할 때 더욱 중요해집니다. 상태 관리 라이브러리 중에서 가장 널리 사용되는 것이 바로 Redux입니다. Redux는 애플리케이션의 상태를 중앙에서 관리할 수 있도록 도와주며, 이로 인해 복잡한 UI와 데이터 흐름을 간편하게 처리할 수 있습니다.

1. Redux란 무엇인가?

Redux는 JavaScript 애플리케이션을 위한 상태 관리 라이브러리로, 다음과 같은 주요 특성을 가지고 있습니다:

  • Centralized State: 모든 상태를 하나의 저장소(store)에 저장하여 애플리케이션의 상태를 예측 가능하게 관리합니다.
  • Immutable State: 상태는 직접 수정할 수 없으며, 항상 새로운 상태를 반환해야 합니다. 이는 상태의 변화를 명확하게 추적할 수 있게 해줍니다.
  • Actions: 상태를 변경하는 유일한 방법은 action을 dispatch하는 것입니다. 액션은 상태 변화의 의도를 담고 있는 객체입니다.
  • Reducers: 상태 변화의 로직을 포함하는 함수로, 현재 상태와 액션을 인자로 받아 새로운 상태를 반환합니다.

2. Redux의 기본 개념

2.1 Store

Redux의 Store는 애플리케이션의 전체 상태를 관리하는 단일 객체입니다. Store는 다음과 같은 3가지 주요 메서드를 제공합니다:

  • getState: 현재 상태를 반환합니다.
  • dispatch: 액션을 store에 전달하여 상태를 변경합니다.
  • subscribe: 상태 변화에 대한 리스너를 등록합니다.

2.2 Actions

액션은 상태 변화를 유발하는 이벤트를 설명하는 객체입니다. 액션은 항상 type 속성을 가지며, 이 타입을 통해 어떻게 상태를 변경할지를 알 수 있습니다. 기본적인 액션의 형태는 다음과 같습니다:

{
        type: 'ACTION_TYPE',
        payload: { /* 필요한 데이터 */ }
    }

2.3 Reducers

리듀서는 현재 상태와 액션을 받아서 새로운 상태를 반환하는 함수입니다. 상태는 불변성을 잃지 않도록 항상 새로운 객체를 반환해야 합니다. 리듀서는 다음과 같이 정의할 수 있습니다:

const reducer = (state = initialState, action) => {
        switch (action.type) {
            case 'ACTION_TYPE':
                // 새로운 상태 반환
                return {
                    ...state,
                    // 변경된 필드
                };
            default:
                return state;
        }
    };

2.4 Dispatching Actions

액션을 디스패치하는 것은 상태를 변경하기 위한 수단입니다. 디스패치된 액션은 리듀서에 전달되며, 리듀서는 이를 처리하여 새로운 상태를 생성합니다. 다음은 액션을 디스패치하는 예제입니다:

store.dispatch({
        type: 'ACTION_TYPE',
        payload: { key: 'value' }
    });

3. Redux의 상태 흐름

Redux의 상태 흐름은 다음과 같이 단순화할 수 있습니다:

  1. User Interaction: 사용자가 UI에서 어떤 액션을 수행합니다.
  2. Action: 이 액션이 액션 객체로 변환되어 dispatch됩니다.
  3. Reducer: 디스패치된 액션은 리듀서로 전달되어 새로운 상태를 생성합니다.
  4. Store Update: 새로운 상태가 Store에 저장됩니다.
  5. UI Update: React 컴포넌트가 새로운 상태를 기반으로 렌더링됩니다.

4. Redux 설치 및 설정

Redux를 프로젝트에 설치하려면 다음 명령어를 통해 readline과 redux, react-redux를 설치해야 합니다:

npm install redux react-redux

4.1 Store 생성하기

스토어는 애플리케이션의 상태를 관리하는 중앙 저장소입니다. 아래와 같이 간단한 스토어를 생성할 수 있습니다:

import { createStore } from 'redux';

const initialState = {
    count: 0
};

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
};

const store = createStore(reducer);

4.2 Provider 설정

리액트 컴포넌트가 스토어에 접근할 수 있도록 Provider로 감싸야 합니다. 아래 예시와 같이 할 수 있습니다:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';

ReactDOM.render(
    
        
    ,
    document.getElementById('root')
);

5. Redux 사용 예제

Redux의 사용을 보여주는 간단한 카운터 애플리케이션을 작성해 보겠습니다.

5.1 Project Structure

src/
├── App.js
├── Counter.js
├── index.js
└── store.js

5.2 store.js

import { createStore } from 'redux';

const initialState = {
    count: 0
};

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        case 'DECREMENT':
            return { count: state.count - 1 };
        default:
            return state;
    }
};

const store = createStore(reducer);

export default store;

5.3 Counter.js

import React from 'react';
import { connect } from 'react-redux';

const Counter = ({ count, increment, decrement }) => {
    return (
        

Count: {count}

); }; const mapStateToProps = (state) => ({ count: state.count }); const mapDispatchToProps = (dispatch) => ({ increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }) }); export default connect(mapStateToProps, mapDispatchToProps)(Counter);

5.4 App.js

import React from 'react';
import Counter from './Counter';

const App = () => {
    return (
        

Redux Counter

); }; export default App;

6. Redux DevTools

Redux DevTools는 상태를 추적하고 디버깅할 수 있는 훌륭한 도구입니다. 개발 중에는 다음과 같이 스토어를 생성할 수 있습니다:

const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

7. 결론

Redux는 리액트 애플리케이션에서 상태를 관리하는 효과적인 도구입니다. 그 구조와 패턴을 이해하고 사용하면 복잡한 UI와 데이터 흐름을 추적하고 관리하는 데 큰 도움이 됩니다. 간단한 카운터 예제를 통해 Redux의 기본 개념을 이해하였다면, 더 복잡한 애플리케이션에서도 Redux를 활용할 수 있을 것입니다. Redux는 강력하지만 그 사용이 항상 필요한 것은 아니므로 애플리케이션의 복잡성에 따라 적절히 사용할 필요가 있습니다.

8. 추가 자료

효과 처리와 비동기 작업 (useEffect), 비동기 데이터 요청 및 API 연동

효과 처리와 비동기 작업 (useEffect), 비동기 데이터 요청 및 API 연동

리액트(React)는 사용자 인터페이스를 구성하기 위한 라이브러리로서, 단일 페이지 애플리케이션(SPA)을 쉽게 구축할 수 있게 해줍니다. 리액트의 주요 개념 중 하나는 컴포넌트입니다. 이 컴포넌트는 UI의 두뇌 역할을 하며, 각 컴포넌트는 자신의 상태(state)와 속성(props)을 관리합니다. 이 글에서는 효과 처리에 널리 사용되는 useEffect 훅과 비동기 작업에 대해 알아보고, 이를 활용하여 API로부터 데이터를 요청하는 방법을 심도 깊게 설명하겠습니다.

1. useEffect 훅 소개

useEffect는 컴포넌트의 생명주기 에서 사이드 이펙트를 처리하기 위한 훅입니다. 주로 다음과 같은 경우에 사용됩니다:

  • 데이터를 가져올 때
  • 구독(subscriptions)을 설정할 때
  • DOM을 수동으로 변경할 때

1.1. 기본 사용법

useEffect는 두 개의 인자를 받습니다: 첫 번째 인자는 사이드 이펙트를 발생시키는 함수이고, 두 번째 인자는 의존성 배열입니다. 의존성 배열에 지정된 값이 변경될 때마다 효과가 다시 실행 됩니다.

import { useEffect } from 'react';

function MyComponent() {
    useEffect(() => {
        // 효과를 처리하는 코드
        document.title = 'My Component Loaded';

        // 정리(clean up) 함수
        return () => {
            document.title = 'React App';
        };
    }, []); // 빈 배열은 컴포넌트가 마운트될 때만 실행됨

    return <div>안녕하세요, 리액트 컴포넌트!</div>;
}

위 예제에서 useEffect는 컴포넌트가 처음 렌더링될 때 한 번만 실행됩니다. 그리고 컴포넌트가 언마운트될 때 제목을 원래 상태로 돌려 놓기 위해 정리(clean up) 함수를 반환합니다.

1.2. 의존성 배열의 중요성

의존성 배열을 설정하면 특정 값이 변경될 때 useEffect가 재실행됩니다. 예를 들어, 다음과 같이 count라는 상태 값을 사용할 수 있습니다:

import React, { useState, useEffect } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        document.title = `카운트: ${count}`;
    }, [count]); // count가 변경될 때마다 실행됨

    return (
        <div>
            <p>현재 카운트: {count}</p>
            <button onClick={() => setCount(count + 1)}>카운트 증가</button>
        </div>
    );
}

이 경우, 버튼을 클릭하여 카운트를 증가시키면 제목이 변경됩니다.

2. 비동기 데이터 요청

리액트에서 API를 통해 데이터를 요청할 때는 일반적으로 fetch API를 사용합니다. 비동기 작업을 수행하기 위해 useEffect와 함께 async/await 패턴을 사용할 수 있습니다. 다음은 데이터를 API로부터 fetch하는 간단한 예제입니다.

import React, { useState, useEffect } from 'react';

function DataFetchingComponent() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await fetch('https://api.example.com/data');
                if (!response.ok) {
                    throw new Error('네트워크 응답에 문제가 있습니다.');
                }
                const result = await response.json();
                setData(result);
            } catch (error) {
                setError(error);
            } finally {
                setLoading(false);
            }
        };

        fetchData(); // 비동기 함수 호출
    }, []); // 빈 배열은 컴포넌트가 마운트될 때만 실행됨

    if (loading) return <p>로딩 중...</p>;
    if (error) return <p>오류: {error.message}</p>;
    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

위 예제에서는 API 요청을 통해 데이터를 가져온 후, 로딩 상태를 관리하고, 오류 처리도 수행합니다. useEffect 내부에서 비동기 함수를 선언하고 호출하는 방식으로 진행됩니다.

3. API 연동 실습

이제 API 연동의 실제 예제를 만들어 보겠습니다. JSONPlaceholder라는 무료 API를 사용하여 사용자 목록을 가져오는 간단한 애플리케이션을 만들어 보겠습니다.

import React, { useState, useEffect } from 'react';

function UserList() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchUsers = async () => {
            setLoading(true);
            try {
                const response = await fetch('https://jsonplaceholder.typicode.com/users');
                if (!response.ok) throw new Error('네트워크 응답에 문제가 있습니다.');
                const data = await response.json();
                setUsers(data);
            } catch (error) {
                setError(error);
            } finally {
                setLoading(false);
            }
        };

        fetchUsers();
    }, []);

    if (loading) return <p>로딩 중...</p>;
    if (error) return <p>오류: {error.message}</p>;

    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name} ({user.email})</li>
            ))}
        </ul>
    );
}

위의 UserList 컴포넌트는 사용자의 목록을 가져와 화면에 표시합니다. 데이터가 로딩 중일 때는 로딩 메시지를 보여주고, 오류가 발생하면 오류 메시지를 표시합니다.

4. 정리

useEffect 훅을 활용하여 컴포넌트의 생명 주기에서 사이드 이펙트를 처리하고, 비동기 작업을 통해 외부 API와 연동하는 과정을 살펴보았습니다. 이 패턴은 리액트 애플리케이션에서 데이터 fetching 및 관리에서 필수적인 부분입니다. 다양한 API를 활용하여 데이터의 상태를 관리하고 사용자에게 더욱 풍부한 경험을 제공할 수 있습니다.

더 나아가 리액트 쿼리(React Query) 같은 라이브러리를 사용하여 API 호출 및 데이터 캐싱을 관리하면 훨씬 더 효율적인 작업이 가능합니다. 하지만 이 글에서는 가장 간단하고 기본적인 방식으로 API 연동을 다루었습니다. 앞으로 더 발전된 기술과 패턴을 배우고 적용해 보시기 바랍니다.

효과 처리와 비동기 작업 (useEffect), 컴포넌트의 라이프사이클과 useEffect의 활용

리액트(React)는 UI 구성을 위한 가장 인기 있는 라이브러리 중 하나로서, 현대 웹 애플리케이션의 프론트엔드 개발에 광범위하게 사용되고 있습니다. 리액트의 핵심 개념 중 하나는 컴포넌트입니다. 컴포넌트는 독립적으로 관리되는 UI의 일부분으로, 각각의 컴포넌트는 상태(state)와 생명주기(lifecycle)를 가집니다. 이 글에서는 리액트의 useEffect 훅에 대해 자세히 설명하고, 컴포넌트의 생명주기와 어떻게 비동기 작업을 관리할 수 있는지에 대해 살펴보겠습니다.

1. 컴포넌트의 생명주기(lifecycle)

리액트 컴포넌트의 생명주기는 크게 세 가지 단계로 나눌 수 있습니다: 마운트(mount), 업데이트(update), 언마운트(unmount)입니다. 각 단계에서는 컴포넌트가 상태 변화나 부모 컴포넌트의 변화에 따라 어떻게 행동하는지를 정의할 수 있습니다.

  • 마운트(Mount): 컴포넌트가 DOM에 처음 렌더링될 때 발생합니다. 이 단계에서 주로 초기 상태를 설정하거나 API를 호출하는 등의 작업을 수행합니다.
  • 업데이트(Update): 컴포넌트의 상태나 props가 변화할 때 발생합니다. 상태 변화에 따라 UI를 재렌더링하거나 관련 작업을 수행할 수 있습니다.
  • 언마운트(Unmount): 컴포넌트가 DOM에서 제거될 때 발생합니다. 이 단계에서 클린업(cleanup) 작업을 수행할 수 있습니다.

2. useEffect 훅의 역할

useEffect 훅은 컴포넌트의 생명주기 메서드를 대체하는 데 사용됩니다. 이 훅은 컴포넌트가 마운트되거나 업데이트 될 때 특정 작업을 수행하도록 정의할 수 있습니다. useEffect는 다음과 같은 용도로 사용됩니다:

  • 데이터 fetching (API 호출)
  • 구독(subscription)
  • DOM 업데이트
  • 타이머 설정 및 클린업

3. useEffect 사용 방법

useEffect 훅은 다음과 같이 사용됩니다:

import React, { useEffect, useState } from 'react';

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

    useEffect(() => {
        // API 호출
        fetch('https://api.example.com/data')
            .then(response => response.json())
            .then(json => setData(json));

        // 클린업 함수 리턴
        return () => {
            // 필요시 클린업 작업 수행
        };
    }, []); // 의존성 배열

    return (
        
{data ?

{data.title}

:

Loading...

}
); };

위의 예제에서 useEffect는 컴포넌트가 처음 마운트될 때 API를 호출하여 데이터를 가져오는 역할을 합니다. 의존성 배열이 빈 배열([])로 설정되어 있기 때문에, 이 effect는 컴포넌트가 최초로 마운트될 때 단 한 번만 실행됩니다. 데이터 fetching이 완료되면 setData를 호출하여 상태를 업데이트하고, 컴포넌트가 다시 렌더링됩니다.

4. 비동기 작업과 useEffect

리액트에서는 비동기 작업을 할 때 주의해야 할 점이 있습니다. 예를 들어, 컴포넌트가 언마운트된 후에도 비동기 작업이 완료되면 상태를 업데이트하려고 할 경우 오류가 발생할 수 있습니다. 이를 방지하려면 효과의 클린업 기능을 활용해야 합니다.

클린업 함수는 useEffect가 반환하는 함수로, 컴포넌트가 언마운트되거나 의존성 배열의 값이 변경될 때 호출됩니다.

5. 예제: 비동기 데이터 fetching과 오류 처리

import React, { useEffect, useState } from 'react';

const DataFetchingComponent = () => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await fetch('https://api.example.com/data');
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                const jsonData = await response.json();
                setData(jsonData);
            } catch (error) {
                setError(error);
            } finally {
                setLoading(false);
            }
        };

        fetchData();

        return () => {
            // 클린업 함수
            setData(null);
            setError(null);
        };
    }, []);

    if (loading) return 

Loading...

; if (error) return

{error.message}

; return (

Data:

{JSON.stringify(data, null, 2)}

);
};

위의 예제에서는 fetchData라는 비동기 함수를 정의하고, 이를 useEffect 내에서 호출합니다. 에러 처리를 위해 setError를 사용하여 API 호출 중 발생할 수 있는 네트워크 오류를 포착합니다. 로딩 상태는 loading 변수를 통해 관리하고 있습니다. 클린업 함수로는 상태 초기화를 수행하여, 컴포넌트가 언마운트되었을 때 상태를 리셋합니다.

6. useEffect의 의존성 배열

useEffect 훅의 두 번째 인자는 의존성 배열입니다. 이 배열은 효과가 재실행될 조건을 설정하는데 사용됩니다. 의존성 배열에 포함된 변수가 변경되면, 해당 effect가 다시 실행됩니다.

useEffect(() => {
    // effect 내용
}, [variable1, variable2]);

위와 같은 형식으로 설정하면, variable1 또는 variable2가 변화할 때마다 effect가 재실행됩니다. 아래에 의존성 배열의 몇 가지 예를 보여드리겠습니다.

예제 1: 상태 변화에 따라 effect 재실행

const TimerComponent = () => {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const timer = setInterval(() => {
            setCount(prevCount => prevCount + 1);
        }, 1000);

        return () => clearInterval(timer);
    }, []);

    return 

Count: {count}

; };

위의 코드에서 setInterval을 통해 1초마다 카운트를 증가시키고 있으며, 의존성 배열이 빈 배열이기 때문에 컴포넌트가 처음 렌더링 될 때만 타이머가 설정됩니다.

예제 2: props 변화에 따른 effect 재실행

const UserProfile = ({ userId }) => {
    const [userData, setUserData] = useState(null);

    useEffect(() => {
        const fetchUserData = async () => {
            const response = await fetch(`https://api.example.com/users/${userId}`);
            const data = await response.json();
            setUserData(data);
        };

        fetchUserData();
    }, [userId]); // userId가 변할 때마다 재실행

    return 
{userData ? userData.name : 'Loading...'}
; };

여기서 userId가 변할 때마다 사용자 데이터가 다시 fetch됩니다. 이는 컴포넌트가 부모 컴포넌트에서 전달받은 props에 따라 렌더링되는 경우 매우 유용합니다.

7. 여러 개의 useEffect 훅 사용하기

리액트에서는 하나의 컴포넌트에서 여러 개의 useEffect 훅을 사용할 수 있습니다. 각 effect는 독립적으로 작동하며, 서로의 의존성에 영향을 미치지 않습니다.

const MultipleEffectsComponent = () => {
    const [value, setValue] = useState(0);
    const [name, setName] = useState('');

    // value 의 변화에 따른 effect
    useEffect(() => {
        console.log('Value changed:', value);
    }, [value]);

    // name 의 변화에 따른 effect
    useEffect(() => {
        console.log('Name changed:', name);
    }, [name]);

    return (
        
setValue(Number(e.target.value))} /> setName(e.target.value)} />
); };

위의 예제에서 valuename 두 개의 상태가 각각의 useEffect 훅에 의해 감지되고, 변화가 발생할 때마다 해당 로그가 출력됩니다.

8. 결론

useEffect 훅은 리액트에서 상태 관리와 비동기 작업을 보다 쉽게 수행할 수 있도록 해줍니다. 컴포넌트의 생명주기를 이해하고, 적절한 타이밍에 효과를 설정하는 것이 매우 중요합니다. 또한, 클린업 함수를 통해 컴포넌트가 언마운트될 때 발생할 수 있는 웹 개발에서의 일반적인 문제를 방지할 수 있습니다. 이 글에서는 useEffect의 사용법과 예제를 중심으로 설명했으며, 이를 통해 비동기 작업과 상태 관리를 효율적으로 구현하는 방법을 배웠습니다. 리액트의 생명주기 관리와 useEffect의 활용이 여러분의 프론트엔드 개발을 더욱 풍부하고 강력하게 만들어줄 것입니다.

9. 추가 학습 자료