<BrowserRouter>
와 <Router>
<BrowserRouter>
는 클라이언트 사이드 라우팅을 위해 라우팅 관련 컴포넌트들의 최상단에 위치시켜야하는 컴포넌트로, react-router-dom
패키지에 속한다. 사실 이는 react-router
패키지에 속해있는 <Router>
컴포넌트를 래핑한 컴포넌트이다. <BrowserRouter>
컴포넌트는 <Router>
컴포넌트를 렌더링할때 props 로 history
객체를 전달하는데, 이 객체는 history
패키지의 createBrowserHistory()
함수를 호출함으로써 생성된다. <Router>
컴포넌트는 마운트되는 순간에 props로 전달받은 history 객체의 프로퍼티인 location 객체를 자신의 지역 상태에 저장한다.<Router>
컴포넌트가 지역상태로서 실시간으로 추적하겠다는 의미이다. <Switch>
<Switch>
는 브라우저의 현재 URL과 매칭되는 첫번째 <Route>
자식 엘리먼트를 렌더링하기 위한 컴포넌트로 react-router
패키지에 속해있다. <RouterContext.Consumer>
컴포넌트를 렌더링함으로써 RouterContext
를 참조한다. 이때 RouterContext
의 location 객체 정보와 children props로 전달받은 각 자식 엘리먼트의 path props 정보를 하나씩 비교한다. 그 과정에서 첫번째로 매칭되는 <Route>
자식 엘리먼트를 렌더링하게된다.<Route>
<Route>
는 props
로 전달받는 path
의 값이 브라우저의 현재 URL과 매칭될때 특정 컴포넌트를 렌더링하는 컴포넌트로 react-router
패키지에 속해있다.<RouterContext.Consumer>
컴포넌트를 렌더링함으로써 RouterContext
를 참조한다.RouterContext
의 location
객체 정보와 props
로 전달받은 path
값을 비교하여 매칭이 되면 component props
로 전달받은 컴포넌트를 렌더링해주고, 아니라면 null
을 렌더링한다. props
로는 RouterContext
와 동일한 구성의 값들 (match
객체, location
객체, history
객체)을 넘겨준다.<Link>
<Link>
는 페이지의 깜빡임(리로드) 없이 내비게이션을 수행하기 위한 컴포넌트로, react-router-dom
패키지에 속해있다.<a>
태그로 렌더링되지만, 일반적인<a>
태그와는 조금 다르게 동작한다. preventDefault()
함수를 호출하여 <a>
태그의 기본 동작을 방지한다. RouterContext
에 존재하는 history
객체를 이용하여 내비게이션을 수행하도록 구현한다.
<a>
태그와의 차이점?
<a>
태그의 기본적인 속성은 페이지를 이동시키면서, 페이지를 아예 새로 불러오게된다. 우리 리액트 앱이 지니고있는 상태들도 초기화되고, 렌더링된 컴포넌트도 모두 사라지며 새로 렌더링을 하게된다.
그렇기에<Link>
컴포넌트를 사용하는데, 이 컴포넌트는 HTML5 History API 를 사용하여 브라우저의 주소만 바꿀뿐, 페이지를 새로 불러오지는 않는다.
history object (객체)
BrowserRouter
는 history
객체를 자동으로 생성하고, 컴포넌트 단위로 history
객체를 컨트롤하기 때문에 커스텀 작업이 어렵다. 컴포넌트 바깥에서 history
객체를 컨트롤하기 위해서는 (ex. action creator) BrowserRouter
대신 Router
를 사용하고, BrowserHistory
를 별도 생성하여 관리해야한다.
history
객체는 history
모듈에서 createBrowserHistory
로 받아올 수 있다.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createBrowserHistory } from 'history';
import { ConnectedRouter } from 'connected-react-router';
import { configureStore } from './store';
const history = createBrowserHistory(); //히스토리 객체 반환
const store = configureStore(history);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
, document.getElementById('root'));
이렇게 작성해주는 방법이 있다고 하는데, 우리는 따로 utils
폴더에 history.js
파일에 분리하여 작성해보도록 하자.
// utils > history.js 파일
import { createBrowserHistory } from "history";
export default createBrowserHistory();
이때
기존 App.js
파일에 이렇게 작성되어있을텐데
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Main from '@/pages/Main';
import connectStore from '@/hoc/connectStore';
const App = () => {
return (
<Router>
<Switch>
<Route path="/" exact component={Main} />
</Switch>
</Router>
);
};
export default connectStore(App);
상단에 import history from "./utils/history";
작성한 history 파일을 import 해오고 <Router history={history}>
Router
에 history
를 props
으로 전달한다. 그러면 각 컴포넌트는 history
, location
, match
객체를 props
로부터 제공받아 사용할 수 있게된다.
⛔ 여기에서 상단 import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
이 부분을 보면 BrowserRouter as Router
이렇게 사용했었는데 이렇게 하니까 history가 제대로 작동하지 않았다. BrowserRouter
를 제거해줬더니 그제서야 잘 작동한다.
최종 코드는 아래와 같다.
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import Main from '@/pages/Main';
import connectStore from '@/hoc/connectStore';
const App = () => {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={Main} />
</Switch>
</Router>
);
};
export default connectStore(App);
❗ 왜 그럴까 생각해보면, 위에서 다뤘던 BrowserRouter
에서
<BrowserRouter>
컴포넌트는<Router>
컴포넌트를 렌더링할때props
로history
객체를 전달하는데, 이 객체는history
패키지의createBrowserHistory()
함수를 호출함으로써 생성된다.
라고 했었는데, 이미 우리는 createBrowserHistory
로 history
객체를 만들어서(history.js
파일에서) 넘겨주는 입장이니까 그냥 Router
를 사용하는게 맞는 방법인 것 같다.
[References]
createBrowserRouter
와 history
객체 : https://kschoi.github.io/react/react-router/
react-router
관련 : https://it-eldorado.tistory.com/113