Redux와 React Router를 활용한 React 애플리케이션 개발 가이드

이상문
13 min readMay 15, 2023

React는 컴포넌트 기반의 사용자 인터페이스를 구축하는 데 매우 유용한 JavaScript 라이브러리이다. 그러나 대규모 애플리케이션의 경우 상태 관리 및 라우팅은 복잡성을 증가시키고 유지 관리를 어렵게 만들 수 있다. 이러한 문제를 해결하기 위해 Redux와 React Router를 사용할 수 있다. 이 글에서는 Redux와 React Router를 사용하여 React 애플리케이션을 개발하는 방법에 대해 정리하고자 한다.

Redux란 무엇인가?

Redux는 React 애플리케이션에서 상태를 효과적으로 관리하는 데 사용되는 JavaScript 라이브러리이다. Redux는 애플리케이션의 상태를 하나의 객체로 관리하고, 이 객체를 변경하기 위해 액션(action)을 디스패치(dispatch)한다. 이 액션은 상태 객체를 변경하는 데 사용되는 데이터와 함께 전달된다.

Redux의 주요 개념들에 대한 간단한 설명

  • 스토어(Store): 애플리케이션의 상태를 저장하는 객체이다. 애플리케이션의 상태를 변경하는 유일한 방법은 액션을 디스패치하여 스토어에 전달하는 것이다.
  • 액션(Action): 상태를 변경하는 요청이다. 액션은 순수한 JavaScript 객체이며, 액션 생성 함수를 통해 생성됩니다.
  • 리듀서(Reducer): 액션이 전달되었을 때, 이전 상태와 액션을 기반으로 새로운 상태를 반환하는 함수이다. 이전 상태를 변경하지 않으며, 새로운 상태를 반환한다.
  • 디스패치(Dispatch) : 액션을 스토어에 전달하는 함수이다. 액션을 디스패치하면, 미들웨어(Middleware)를 거쳐 리듀서가 호출되고, 새로운 상태를 반환한다.

connect 함수

connect 함수는 Redux 스토어를 React 컴포넌트에 연결하는 데 사용된다. 이 함수는 두 가지 인자를 받는다. 첫 번째 인자는 mapStateToProps 함수이며, 이 함수는 스토어의 상태를 컴포넌트의 props로 매핑하는 역할을 한다. 두 번째 인자는 mapDispatchToProps 함수이며, 이 함수는 액션 디스패치 함수를 props로 매핑하는 역할을 한다.

Connected Component

Connected Component는 connect 함수를 사용하여 Redux 스토어에 연결된 React 컴포넌트를 말한다. Connected Component는 스토어의 상태를 구독하고, 상태가 변경될 때마다 자동으로 다시 렌더링된다.

스토어의 상태 및 디스패치 기능 사용하기

Provider 컴포넌트를 불러온다.

import { Provider } from 'react-redux';

createStore 함수를 사용하여 스토어를 생성한다.

import { createStore } from 'redux';

function reducer(state = {}, 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);

createStore 함수는 reducer 함수를 인자로 받아 스토어를 생성한다. reducer 함수는 현재 상태와 액션 객체를 인자로 받아 새로운 상태를 반환한다.

Provider 컴포넌트를 사용하여 애플리케이션의 상위 컴포넌트를 감싸고 store를 전달합니다.

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

connect 함수를 사용하여 컴포넌트를 스토어에 연결한다. connect 함수는 두 개의 인자를 받는다.

  • mapStateToProps: 스토어의 상태를 인자로 받아 컴포넌트의 props로 매핑한다.
  • mapDispatchToProps: 액션 생성 함수를 인자로 받아 컴포넌트의 props로 매핑한다. 이렇게 하면 컴포넌트에서 액션을 디스패치할 수 있게 된다.
import { connect } from 'react-redux';

function Counter(props) {
return (
<div>
<h1>{props.count}</h1>
<button onClick={props.increment}>Increment</button>
<button onClick={props.decrement}>Decrement</button>
</div>
);
}

function mapStateToProps(state) {
return {
count: state.count
};
}

function mapDispatchToProps(dispatch) {
return {
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
};
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

connect 함수는 또한 스토어의 상태가 변경될 때마다 컴포넌트를 다시 렌더링하도록 구독(subscribe)하는 기능을 한다. 이렇게 하면 스토어의 상태가 변경될 때마다 컴포넌트가 자동으로 업데이트된다.

React Router

React Router는 React 애플리케이션에서 다른 페이지에 매핑되는 경로를 처리하는 데 사용되는 라이브러리이다. React Router를 사용하면 URL 경로에 따라 다른 컴포넌트를 렌더링할 수 있다.

React Router를 사용하여 다른 페이지에 매핑되는 경로를 처리하는 방법

React Router는 브라우저의 URL을 사용하여 애플리케이션 내의 다른 페이지로 이동할 수 있다. 이를 위해서는 react-router-dom 패키지에서 제공하는 라우팅 컴포넌트들을 사용해야 한다.

가장 기본적인 라우팅 컴포넌트는 Route 이다. Route 컴포넌트는 경로와 그에 해당하는 컴포넌트를 매핑시켜주는 역할을 한다.

import { Route } from 'react-router-dom';

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

위의 코드에서 path 속성은 URL 경로를 지정하고, component 속성은 해당 경로에 접근할 때 렌더링할 컴포넌트를 지정한다. exact 속성은 정확한 경로와 매칭되어야만 해당 컴포넌트가 렌더링되도록 한다.

또 다른 라우팅 컴포넌트로는 Switch 컴포넌트가 있다. Switch 컴포넌트는 여러 개의 Route 컴포넌트 중에서 가장 먼저 매칭되는 컴포넌트만 렌더링하도록 한다. 이를 통해 중복 매칭이나 오류를 방지할 수 있다.

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

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

위의 코드에서는 Switch 컴포넌트로 감싸진 Route 컴포넌트들 중에서 경로와 일치하는 첫 번째 컴포넌트만 렌더링한다. NotFound 컴포넌트는 위에서 매칭되는 경로가 없을 때 렌더링된다.

BrowserRouter와 HashRouter의 차이

react-router-dom에서 라우팅 기능을 사용할 때는 BrowserRouterHashRouter를 이용해서 사용할 수 있다.

BrowserRouter

BrowserRouter는 HTML5의 History API를 이용하여 브라우저의 URL을 관리하는 방식이다. BrowserRouter는 브라우저의 주소창에 입력한 URL과 관련된 정보를 이용하여 렌더링을 수행한다.

예를 들어, 사용자가 /home 경로로 이동하면 브라우저의 URL이 /home으로 바뀌고, 해당 URL을 이용하여 렌더링이 수행된다.

import { BrowserRouter as Router, Route, Link } from "react-router-dom";

function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>

<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</div>
</Router>
);
}

HashRouter

HashRouter는 URL의 해시(hash)를 이용하여 브라우저의 URL을 관리하는 방식이다. HashRouter는 URL의 해시를 이용하기 때문에, 브라우저의 URL이 실제 URL과 다르게 보일 수 있다.

예를 들어, 사용자가 /home 경로로 이동하면 브라우저의 URL이 #/home으로 바뀌고, 해당 URL을 이용하여 렌더링이 수행된다.

import { HashRouter as Router, Route, Link } from "react-router-dom";

function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>

<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</div>
</Router>
);
}

어떤 것을 사용해야 할까?

BrowserRouter는 HTML5의 history API를 사용하여 URL을 관리합니다. 이는 브라우저의 주소 표시줄에서 사용하는 것과 동일한 방식이다. 따라서 BrowserRouter는 직관적이고 예측 가능한 URL을 제공하며, 일반적으로 사용자 친화적인 URL을 사용하는 경우 적합하다. 하지만 이를 지원하지 않는 브라우저에서는 작동하지 않을 수 있다.

반면, HashRouter는 URL의 일부로 해시(#) 기호를 사용한다. 이는 브라우저의 주소 표시줄에서는 보이지 않지만, 브라우저는 URL의 해시 부분을 변경하면 페이지가 다시 로드되지 않고 새 URL로 라우팅된다. 이 방법은 모든 브라우저에서 동작하므로 특별한 환경이 아닌 한 대부분의 경우에는 잘 동작한다. 그러나 일부 사용자는 보안상의 이유로 해시 URL을 선호하지 않을 수 있다.

프로젝트 요구 사항과 개발자의 기술적 선호도에 따라 라우터를 선택해야 한다.

React Router를 사용하여 다른 페이지에 매핑되는 경로를 처리하는 방법

React Router는 다양한 컴포넌트와 라우팅 기능을 제공한다. BrowserRouter나 HashRouter 컴포넌트를 사용하여 웹 페이지에서 URL을 사용할 수 있게 만들고, Route 컴포넌트를 사용하여 경로에 매핑되는 구성 요소를 지정할 수 있다.

예를 들어, 블로그에서 게시물을 보여주기 위해 “/posts/:id” 경로를 사용하는 경우, 다음과 같이 Route 컴포넌트를 사용하여 해당 경로에 매핑되는 구성 요소를 지정할 수 있다.

import { BrowserRouter as Router, Route } from 'react-router-dom';
import Post from './Post';

function App() {
return (
<Router>
<div>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Route path="/posts/:id" component={Post} />
</div>
</Router>
);
}

위 코드에서는 BrowserRouter 컴포넌트로 Router를 감싸고, Route 컴포넌트를 사용하여 URL 경로와 해당 경로에 매핑되는 구성 요소를 지정한다. exact 속성을 사용하여 경로가 정확히 일치해야 하는지를 지정할 수 있다.

Post 컴포넌트에서는 React Router의 match 객체를 사용하여 URL 매개변수에 접근할 수 있다. match.params 객체는 URL 매개변수를 키-값 쌍으로 포함하며, Post 컴포넌트에서 이를 사용하여 게시물의 ID를 가져올 수 있습니다.

import { useParams } from 'react-router-dom';

function Post() {
const { id } = useParams();
// 게시물 ID를 사용하여 데이터 가져오기
// ...
}

마무리

이번 글에서는 Redux와 React Router의 개념과 사용 방법에 대해 정리했다. Redux는 React 애플리케이션에서 상태 관리를 효율적으로 처리하기 위한 도구로, 단방향 데이터 흐름을 따른다. 또한, React Router는 React 애플리케이션에서 다른 페이지에 매핑되는 경로를 처리하기 위한 라이브러리로, 브라우저 히스토리 API를 사용하여 URL을 처리한다.

Redux와 React Router는 각각 독립적으로 사용할 수도 있지만, 대부분의 React 프로젝트에서 함께 사용된다. Redux는 애플리케이션의 상태를 중앙 집중적으로 관리하여 예측 가능한 상태 변화를 유지하고, React Router는 애플리케이션의 다른 페이지에 매핑되는 경로를 쉽게 처리할 수 있도록 한다.

--

--

이상문

software developer working mainly in field of streaming, using C++, javascript