라우팅 : 다른 주소에 다른 뷰를 보여 주는 것
전통적인 웹 페이지에서는 다른 뷰를 렌더링 할 때마다, 서버에서 html을 받아왔다.
반면 SPA에서는 애플리케이션을 브라우저에 불러와서 실행시킨 이후, 뷰의 렌더링을 서버가 아닌 브라우저에서 자바스크립트로 제어한다.
(SPA에서는 html파일이 하나이다)
리액트 라이브러리에는 라우팅 기능이 내장되어 있지 않기에, 브라우저의 API를 직접 사용하거나 라이브러리를 사용한다.
react-router-dom 라이브러리는 클라이언트 사이드 라우팅뿐만 아니라, 서버 사이드 렌더링에도 라우팅을 도와주는 컴포넌트들을 제공한다.
앱의 규모가 커지면 자바스크립트 파일이 너무 비대해진다.
(페이지 로딩 시에 사용자가 실제로 방문하지 않을 수도 있는 페이지의 스크립트까지 전부 불러오기 때문)
브라우저에서 자바스크립트를 사용하여 라우팅을 관리하는 것은 자바스크립트를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집해가지 못한다.
또한, 자바스크립트가 실행될 때까지 페이지가 비어 있기 때문에 자바스크립트 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있다
// ...
import { BrowserRouter } from "react-router-dom';
// ...
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root'),
);
// ...
프로젝트에 리액트 라우터를 적용할 때는 src/index.js파일에서 react-router-dom에 내장되어 있는 BrowserRouter컴포넌트를 사용하여 최상위 컴포넌트를 감싸면 된다.
BroswerRouter컴포넌트
웹 애플리케이션에 HTML5의 History API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경하고,
현재 주소에 관련됭 정보를 props로 쉽게 조회하거나 사용할 수 있도록 해준다.
// ...
import { Route } from 'react-router-dom';
// ...
// ...
<Route path="주소" component={보여 줄 컴포넌트} />
// ...
import React from 'react';
import { Route } from 'react-router-dom';
import Home from './component/Home';
import About from './component/About';
const App = () => {
return (
<>
<Route path="/" component={Home} exact={true} />
<Route path="/about" component={About} />
</>
);
};
export default App;
위 예제에서 첫번째 Route컴포넌트에는 exact prop에 true값을 전달해주었다.
exact prop에 true값을 전달해주지 않으면 주소 규칙 상 "/"가 "/about"에 포함되기 때문에 "/about" 주소값에서 두 컴포넌트가 같이 렌더링되기 때문이다.
리액트에서도 a태그를 사용할 수는 있으나, 이 경우 페이지를 전환하는 과정에서 페이지를 새로 불러오기 때문에 애플리케이션이 들고 있던 상태들을 모두 날려버리게 된다.
(렌더링된 컴포넌트들도 모두 사라지고 처음부터 다시 렌더링하게 된다)
Link 컴포넌트는 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 상태에서 HTML5 History API를 사용하여 페이지의 주소만 변경해 준다.
import React from 'react';
import { Route, Link } from 'react-router-dom';
import Home from './component/Home';
import About from './component/About';
const App = () => {
return (
<>
<ul>
<li>
<Link to="/">홈</Link>
</li>
<li>
<Link to="/about">소개</Link>
</li>
</ul>
<Route path="/" component={Home} exact={true} />
<Route path="/about" component={About} />
</>
);
};
export default App;
import React from 'react';
import { Route, Link } from 'react-router-dom';
import Home from './component/Home';
import About from './component/About';
const App = () => {
return (
<>
<Route path="/" component={Home} exact={true} />
<Route path={['/about', '/info']} component={About} />
</>
);
};
export default App;
<Route path="/somePath/:someParam" component={someComponent} />
<Link to={`/somePath/${파라미터값}`}>다른 뷰</Link>
const someComponent = ({ match }) => {
const { someParam } = match.params;
// ...
}
<Link to={`/somePath?someQuery=${쿼리값}`}>다른 뷰</Link>
// ...
import qs from "qs";
// ...
const someComponent = ({ location }) => {
const query = qs.parse(location.search, {
ignoreQueryPrefix: true // 쿼리 문자열 맨 앞의 ?를 생략
});
const { someQuery } = query;
// ...
}
전달된 쿼리 값은 해당 컴포넌트의 location prop의 search로 접근하여 사용할 수 있는데, 이 값이 쿼리 문자열이기 때문에 qs라이브러리를 이용하여 객체로 변환시켜주는 것이 일반적이다.
주의할 점은, qs라이브러리를 통한 변환 과정에서 각 쿼리 값은 전부 문자열로 변환되므로 숫자 자료형이나 boolean 자료형을 쿼리 값으로 넘겨준 경우 이를 고려해줘야 한다는 것이다.
라우트 내부에 또 라우트를 정의하는 것
(=라우트로 사용되고 있는 컴포넌트의 내부에 Route 컴포넌트를 또 사용하는 것)
아래의 예제는 라우트 내부에 또 라우트를 설정한 예시이다.
import React from 'react';
import { Link, Route } from 'react-router-dom';
import Profile from './Profile';
const Profiles = () => {
return (
<div>
<h3>사용자 목록 : </h3>
<ul>
<li>
<Link to="/profiles/velopert">velopert</Link>
</li>
<li>
<Link to="/profiles/woobuntu">woobuntu</Link>
</li>
</ul>
<Route
path="/profiles"
exact // JSX에서 props를 설정할 때 값을 생략하면 자동으로 true로 설정
render={() => <div>사용자를 선택해주세요</div>}
/>
<Route path="/profiles/:username" component={Profile} />
</div>
);
};
export default Profiles;
라우트로 사용된 컴포넌트에는 match, location외에도 history라는 객체가 prop으로 전달된다.
이 객체를 통해 컴포넌트 내의 메서드에서 라우터 API를 호출할 수 있다.
아래는 클래스형 컴포넌트에서의 history객체 사용 예제이다.
import React, { Component } from 'react';
class HistoryClass extends Component {
handleGoBack = () => {
this.props.history.goBack();
};
handleGoHome = () => {
this.props.history.push('/');
};
// 컴포넌트가 마운트된 뒤 컴포넌트 인스턴스의 unblock property에 함수 설정
componentDidMount() {
this.unblock = this.props.history.block('정말 떠나시겠습니까?');
}
// 컴포넌트가 언마운트되기 직전에 컴포넌트 인스턴스에 unblock property가 있다면
// 해당 property를 함수로써 호출
componentWillUnmount() {
if (this.unblock) {
this.unblock();
}
}
render() {
return (
<div>
<button onClick={this.handleGoBack}>뒤로</button>
<button onClick={this.handleGoHome}>홈으로</button>
</div>
);
}
}
export default HistoryClass;
import React, { useEffect, useRef } from 'react';
const HistoryFunction = ({ history }) => {
const handleGoBack = () => {
history.goBack();
};
const handleGoHome = () => {
history.push('/');
};
let unblock = useRef(undefined);
function stopAsking() {
if (unblock.current) {
unblock.current();
}
}
useEffect(() => {
unblock.current = history.block('정말 떠나시겠습니까?');
return stopAsking;
}, [history]);
return (
<div>
<button onClick={handleGoBack}>뒤로</button>
<button onClick={handleGoHome}>홈으로</button>
</div>
);
};
export default HistoryFunction;
함수형 컴포넌트에서 this 바인딩은 최대한 지양하는 편이 좋다고 생각하기 때문에, unblock을 지역 변수로 삼아 렉시컬 스코프를 활용했다.
(애초에 이 접근부터 틀린 건 아닌지 모르겠다)
useEffect의 dependency로 빈 배열을 전달해주어야 콜백 내부의 코드가 마운트된 이후에만 실행되며, 뒷정리 함수는 언마운트 직전에 실행된다.
dependency에 특정 값을 전달하면 해당 값의 업데이트 직전에 뒷정리 함수가 실행되고, 업데이트 시점에 콜백 내부의 코드가 실행된다.
다만 history객체가 업데이트 되는 경우는 라우트를 통해 해당 컴포넌트에 들어갈 때와 나올 때이므로, 이 경우엔 dependency에 history가 있는 것이 문제가 되지 않는다고 판단했다
함수형 컴포넌트던, 클래스형 컴포넌트던 'history.goBack()'에는 history.block이 작동하지 않는데 이유를 모르겠다.
withRouter 함수는 HoC(Higher-order Component)이다.
(무슨 말일까)
라우트로 사용된 컴포넌트가 아니어도 match, location, history prop에 접근할 수 있게 해준다.
// ...
import { withRouter } from "react-router-dom";
// ...
const someComponent = ({ location, match, history }) => {
//...
}
export default withRouter(someComponent);
우리가 흔히 아는 조건문 switch로 생각해도 무방하다
여러 Route를 감싸서 그 중 현재 주소값과 일치하는 단 하나의 라우트만을 렌더링시킨다.
모든 규칙과 일치하지 않을 때 보여줄 Not Found 페이지의 구현에도 사용할 수 있다.
// ...
import { Switch } from "react-router-dom";
// ...
const someComponent = () => {
return (
<>
// ...
<Switch>
// Route 컴포넌트들 나열
<Route
render={({ location }) => (/* 404페이지에 사용할 JSX */)} />
</Switch>
</>
);
};
NavLink는 현재 주소값과 Link의 주소값이 일치하는 경우 특정 스타일 혹은 CSS를 적용할 수 있는 컴포넌트이다.
activeStyle : 링크가 활성화되었을 때의 스타일
activeClassName : 링크가 활성화되었을 때의 CSS클래스를 props로 전달하는 용도
// ...
import { NavLink } from "react-router-dom";
// ...
const someComponent = () => {
const activeStyle = {
// 활성화되었을 때의 style
};
return (
<>
// ...
<NavLink activeStyle={activeStyle} to="주소값">
뭐시기
</NavLink>
// ...
</>
);
};