출처: <리액트를 다루는 기술> 책
리액트 같은 라이브러리나 프레임워크를 사용하여 사용자의 브라우저가 담당하도록 하고, 우선 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트
만약 새로운 데이터가 필요하면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용 가능
서버에서 사용자에게 제공하는 페이지는 한 종류이지만, 해당 페이지에서 로딩된 자바스크립트와 현재 사용자 브라우저의 주소 상태에 따라 다양한 화면을 보여 줄 수 있음
라우팅
: 다른 주소에 다른 화면을 보여 주는 것
리액트 라이브러리 자체에 이 기능이 내장되어 있지는 않음 ➡️ 브라우저의 API를 직접 사용하여 이를 관리하거나, 라이브러리를 사용하여 이 작업을 더욱 쉽게 구현할 수 있음
react-router
: 클라이언트 사이드에서 이루어지는 라우팅을 아주 간단하게 구현할 수 있도록 해줌. 서버 사이드 렌더링 할 때도 라우팅을 도와주는 컴포넌트들을 제공해줌.앱의 규모가 커지면 JS 파일이 너무 커짐
JS를 실행하지 않는 일반 크롤러에서는 페이지의 정보를 제대로 수집해 가지 못할 수 있음
JS가 실행될 때까지 페이지가 비어 있기 때문에 JS 파일이 로딩되어 실행되는 짧은 시간 동안 흰 페이지가 나타날 수 있음
➡️ 이런 문제들은 서버 사이드 렌더링(server-side-rendering)을 통해 해결 가능
$ yarn create react-app router-tutorial
$ cd router-tutorial
$ yarn add react-router-dom
src/index.js 파일
import React from ‘react‘;
import ReactDOM from ‘react-dom‘;
import { BrowserRouter } from ‘react-router-dom‘;
import ‘./index.css‘;
import App from ‘./App‘;
import * as serviceWorker from ‘./serviceWorker‘;
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById(‘root‘)
);
serviceWorker.unregister();
src 디렉터리에 Home.js
생성
import React from 'react';
const Home = () => {
return (
<div>
<h1>홈</h1>
<p>홈, 그 페이지는 가장 먼저 보여지는 페이지.</p>
</div>
);
};
export default Home;
src 디렉터리에 About.js
생성
import React from 'react';
const About = () => {
return (
<div>
<h1>소개</h1>
<p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
</div>
);
};
export default About;
예시: <Route path=“주소규칙“ component={보여 줄 컴포넌트} />
App.js
파일에서 기존 코드를 모두 제거 후, Route 컴포넌트를 사용하여 Home 컴포넌트 or About 컴포넌트를 보여 주도록 설정하기import React from ‘react‘;
import { Route } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
const App = () => {
return (
<div>
<Route path=“/“ component={Home} />
<Route path=“/about“ component={About} />
</div>
);
};
export default App;
⬇ 주소창에 localhost:3000/about 입력하면 About 컴포넌트만 나오는 것이 아니라 두 컴포넌트 모두 나타남
이유: /about 경로가 / 규칙에도 일치하기 때문
📍 Home을 위한 Route 컴포넌트 사용할 때 exact라는 props를 true로 설정하면 해결 가능
app.js 파일
import React from ‘react‘;
import { Route } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
const App = () => {
return (
<div>
<Route path=“/“ component={Home} exact={true} />
<Route path=“/about“ component={About} />
</div>
);
};
export default App;
다시 브라우저를 확인해보면 이제 about 컴포넌트만 뜸!
7) Link 컴포넌트를 사용해 다른 주소로 이동하기
<Link to="주소">내용</Link>
+) 일반 웹 애플리케이션에서는 a 태그를 사용하여 페이지를 전환하는데, 리액트 라우터를 사용할 때는 이 태그를 직접 사용하면 안됨. ( a 태그는 페이지를 전환하는 과정에서 페이지를 새로 불러오기 때문에 애플리케시연이 들고 있던 상태들을 모두 날리게 됨. 렌더링된 컴포넌트들도 모두 사라지고 다시 처음부터 렌더링하게 됨.)
App.js
import React from ‘react‘;
import { Route, Link } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
const App = () => {
return (
<div>
<ul>
<li>
<Link to=“/“>홈</Link>
</li>
<li>
<Link to=“/about“>소개</Link>
</li>
</ul>
<hr />
<Route path=“/“ component={Home} exact={true} />
<Route path=“/about“ component={About} />
</div>
);
};
export default App;
이제 페이지 상단에 있는 링크를 누르면 페이지가 전환됨.
이전 버전
에서 여러 개의 path에 같은 컴포넌트를 보여 줄 때import React from ‘react‘;
import { Route } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
const App = () => {
return (
<div>
<Route path=“/“ component={Home} exact={true} />
<Route path=“/about“ component={About} />
<Route path=“/info“ component={About} />
</div>
);
};
export default App;
이렇게 Route를 두 번 사용하는 대신, path props를 배열로 설정해주면 여러 경로에서 같은 컴포넌트를 보여 줄 수 있음
import React from 'react';
import { Route } from 'react-router-dom';
import About from './About';
import Home from './Home';
const App = () => {
return (
<div>
<Route path="/" component={Home} exact={true} />
<Route path={['/about', '/info']} component={About} />
</div>
);
};
export default App;
페이지 주소를 정의할 때 유동적인 값을 전달하고 싶을 때는 파라미터
와 쿼리
로 분리
일반적으로 파라미터
는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용, 쿼리
는 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용
import React from ‘react‘;
const data = {
velopert: {
name: ‘김민준‘,
description: ‘리액트를 좋아하는 개발자‘
},
gildong: {
name: ‘홍길동‘,
description: ‘고전 소설 홍길동전의 주인공‘
}
};
const Profile = ({ match }) => {
const { username } = match.params;
const profile = data[username];
if (!profile) {
return <div>존재하지 않는 사용자입니다.</div>;
}
return (
<div>
<h3>
{username}({profile.name})
</h3>
<p>{profile.description}</p>
</div>
);
};
export default Profile;
URL 파라미터를 사용할 때는 라우터로 사용되는 컴포넌트에서 받아오는 match
라는 객체 안의 params 값을 참조
match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보 들어있음
App 컴포넌트에서 Profile 컴포넌트를 위한 라우트 정의하기
여기서 사용할 path 규칙에는 /profiles/:username이라고 넣어 주면 됨 ➡️ match.params.username
값을 통해 현재 username 값을 조회할 수 있음
라우트 정의 후 상단에 각 프로필 페이지로 이동하는 링크 추가
import React from ‘react‘;
import { Route, Link } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
import Profile from ‘./Profile‘;
const App = () => {
return (
<div>
<ul>
<li>
<Link to=“/“>홈</Link>
</li>
<li>
<Link to=“/about“>소개</Link>
</li>
<li>
<Link to=“/profile/velopert“>velopert 프로필</Link>
</li>
<li>
<Link to=“/profile/gildong“>gildong 프로필</Link>
</li>
</ul>
<hr />
<Route path=“/“ component={Home} exact={true} />
<Route path={[‘/about‘, ‘/info‘]} component={About} />
<Route path=“/profile/:username“ component={Profile} />
</div>
);
};
export default App;
About 페이지에서 쿼리 받아오기
https://localhost:3000/about?detail=true 주소로 들어갔을 때의 location 객체
형태
{
“pathname”: “/about”,
“search”: “?detail=true”,
“hash”: “”
}
qs 라이브러리 설치
$ yarn add qs
About 컴포넌트에서 location.search 값에 있는 detail이 true인지 아닌지에 따라 추가 정보를 보여 주도록 만들기
import React from ‘react‘;
import qs from ‘qs‘;
const About = ({ location }) => {
const query = qs.parse(location.search, {
ignoreQueryPrefix: true // 이 설정을 통해 문자열 맨 앞의 ?를 생략합니다.
});
const showDetail = query.detail === ‘true‘; // 쿼리의 파싱 결과 값은 문자열입니다.
return (
<div>
<h1>소개</h1>
<p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
{showDetail && <p>detail 값을 true로 설정하셨군요!</p>}
</div>
);
};
export default About;
3) 필요하면 값의 형태 변경
❗ 쿼리를 사용할 때는 쿼리 문자열을 객체로 파싱하는 과정에서 결과 값은 언제나 문자열이라는 점 주의
?value=1
or ?value=true
와 같이 숫자나 논리 자료형(boolean)을 사용한다고 해서 해당 값이 원하는 형태로 변환되지 않고 "1", "true"처럼 문자열 형태로 받아짐
➡️ 숫자를 받아 와야 하면 parseInt
, 논리 자료형 값을 사용해야 하면 정확히 "true" 문자열이랑 일치하는지 확인
서브 라우트
: 라우트 내부에 또 라우트를 정의 ➡️ 라우트로 사용하고 있는 컴포넌트 내부에 Route 컴포넌트를 또 사용해주면 됨
기존의 App 컴포넌트에서는 두 종류의 프로필 링크 보여줌 ➡️ 이를 잘라내서 프로필 링크를 보여주는 Profiles
라우트 컴포넌트를 만들고, 그 안에서 Profile
컴포넌트를 서브 라우트로 사용
profiles.js
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/gildong">gildong</Link>
</li>
</ul>
<Route
path="/profiles"
exact
render={() => <div>사용자를 선택해 주세요.</div>}
/>
<Route path="/profiles/:username" component={Profile} />
</div>
);
};
export default Profiles;
위 코드에서 첫 번째 Route 컴포넌트에는 component 대신 render라는 props 넣어 줌.
컴포넌트 자체를 전달하는 것이 아니라, 보여 주고 싶은 JSX를 넣어 줄 수 있음.
지금처럼 따로 컴포넌트를 만들기 애매한 상황에 사용하거나 컴포넌트에 props를 별도로 넣어 주고 싶을 때도 사용 가능
JSX에서 props를 설정할 때 값을 생략하면 자동으로 true 설정
ex) 현재 Profile 컴포넌트의첫 번째 Route에서 exact={true} 대신 그냥 exact라고만 적어 줬지만 의미는 같음
App.js
기존 App 컴포넌트에 있던 프로필 링크를 지우고 Profiles 컴포넌트를 /profiles 경로에 연결 & 해당 경로로 이동하는 링크 추가
import React from ‘react‘;
import { Route, Link } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
import Profiles from ‘./Profiles‘;
const App = () => {
return (
<div>
<ul>
<li>
<Link to=“/“>홈</Link>
</li>
<li>
<Link to=“/about“>소개</Link>
</li>
<li>
<Link to=“/profiles“>프로필</Link>
</li>
</ul>
<hr />
<Route path=“/“ component={Home} exact={true} />
<Route path={[‘/about‘, ‘/info‘]} component={About} />
<Route path=“/profiles“ component={Profiles} />
</div>
);
};
export default App;
history
객체: 라우트로 사용된 컴포넌트에 match, location과 함께 전달되는 props 중 하나 ➡️ 이 객체를 통해 컴포넌트 내에 구현하는 메서드에서 라우터 API를 호출할 수 있음history 활용 예) 특정 버튼을 눌렀을 때 뒤로 가기, 로그인 후 화면 전환, 다른 페이지로 이탈하는 것을 방지
HistorySample.js
컴포넌트 생성
import React, { Component } from ‘react‘;
class HistorySample extends Component {
// 뒤로 가기
handleGoBack = () => {
this.props.history.goBack();
};
// 홈으로 이동
handleGoHome = () => {
this.props.history.push(‘/‘);
};
componentDidMount() {
// 이것을 설정하고 나면 페이지에 변화가 생기려고 할 때마다 정말 나갈 것인지를 질문함
this.unblock = this.props.history.block(‘정말 떠나실 건가요?‘);
}
componentWillUnmount() {
// 컴포넌트가 언마운트되면 질문을 멈춤
if (this.unblock) {
this.unblock();
}
}
render() {
return (
<div>
<button onClick={this.handleGoBack}>뒤로</button>
<button onClick={this.handleGoHome}>홈으로</button>
</div>
);
}
}
export default HistorySample;
App.js
/history 경로에 해당 컴포넌트가 보이도록 설정
import React from ‘react‘;
import { Route, Link } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
import Profiles from ‘./Profiles‘;
import HistorySample from ‘./HistorySample‘;
const App = () => {
return (
<div>
<ul>
<li>
<Link to=“/“>홈</Link>
</li>
<li>
<Link to=“/about“>소개</Link>
</li>
<li>
<Link to=“/profiles“>프로필</Link>
</li>
<li>
<Link to=“/history“>History 예제</Link>
</li>
</ul>
<hr />
<Route path=“/“ component={Home} exact={true} />
<Route path={[‘/about‘, ‘/info‘]} component={About} />
<Route path=“/profiles“ component={Profiles} />
<Route path=“/history“ component={HistorySample} />
</div>
);
};
export default App;
링크를 눌러서 현재 페이지를 이탈하려고 할 때 아래와 같은 메시지 창이 뜸
withRouter
함수: HoC(Higher-order Component) ➡️ 라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체를 접근할 수 있게 해줌withRouterSample.js
컴포넌트 생성
import React from ‘react‘;
import { withRouter } from ‘react-router-dom‘;
const WithRouterSample = ({ location, match, history }) => {
return (
<div>
<h4>location</h4>
<textarea
value={JSON.stringify(location, null, 2)}
rows={7}
readOnly={true}
/>
<h4>match</h4>
<textarea
value={JSON.stringify(match, null, 2)}
rows={7}
readOnly={true}
/>
<button onClick={() => history.push(‘/‘)}>홈으로</button>
</div>
);
};
export default withRouter(WithRouterSample);
위의 코드처럼 withRouter를 사용할 때는 컴포넌트를 내보내 줄 때 함수로 감싸 줌
JSON.stringify의 두 번째 파라미터와 세 번째 파라미터를 위와 같이 null, 2로 설정해 주면 JSON에 들여쓰기가 적용된 상태로 문자열이 만들어짐
Profiles
컴포넌트에 렌더링하기
import React from ‘react‘;
import { Link, Route } from ‘react-router-dom‘;
import Profile from ‘./Profile‘;
import WithRouterSample from ‘./WithRouterSample‘;
const Profiles = () => {
return (
<div>
(…)
<WithRouterSample />
</div>
);
};
export default Profiles;
match 객체를 보면 params가 비어 있음 ➡️ withRouter를 사용하면 현재 자신을 보여 주고 있는 라우트 컴포넌트(현재 Profiles)를 기준으로 match가 전달됨
Profiles를 위한 라우트를 설정할 때는 path=" /profiles"
라고만 입력했기 때문에 username 파라미터를 읽어 오지 못하는 상태 ➡️ WithRouterSample
컴포넌트를 Profiles에서 지우고 Profile 컴포넌트에 넣으면 match 쪽에 URL 파라미터가 제대로 보이게 됨
Profile.js
import React from ‘react‘;
import { withRouter } from ‘react-router-dom‘;
import WithRouterSample from ‘./WithRouterSample‘;
(…)
const Profile = ({ match }) => {
const { username } = match.params;
const profile = data[username];
if (!profile) {
return <div>존재하지 않는 사용자입니다.</div>;
}
return (
<div>
(…)
<WithRouterSample />
</div>
);
};
export default withRouter(Profile);
Switch
컴포넌트: 여러 Route를 감싸서 그중 일치하는 단 하나의 라우트만을 렌더링시킴App.js
import React from ‘react‘;
import { Route, Link, Switch } from ‘react-router-dom‘;
import About from ‘./About‘;
import Home from ‘./Home‘;
import Profiles from ‘./Profiles‘;
import HistorySample from ‘./HistorySample‘;
const App = () => {
return (
<div>
<ul>
<li>
<Link to=“/“>홈</Link>
</li>
<li>
<Link to=“/about“>소개</Link>
</li>
<li>
<Link to=“/profiles“>프로필</Link>
</li>
<li>
<Link to=“/history“>History 예제</Link>
</li>
</ul>
<hr />
<Switch>
<Route path=“/“ component={Home} exact={true} />
<Route path={[‘/about‘, ‘/info‘]} component={About} />
<Route path=“/profiles“ component={Profiles} />
<Route path=“/history“ component={HistorySample} />
<Route
// path를 따로 정의하지 않으면 모든 상황에 렌더링됨
render={({ location }) => (
<div>
<h2>이 페이지는 존재하지 않습니다:</h2>
<p>{location.pathname}</p>
</div>
)}
/>
</Switch>
</div>
);
};
export default App;
NavLink
: Link와 비슷. 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 CSS 클래스를 적용할 수 있는 컴포넌트
NavLink에서 링크가 활성화되었을 때의 스타일을 적용할 때는 activeStyle
값을, CSS 클래스를 적용할 때는 activeClassName
값을 props로 넣어 주면 됨
Profiles.js
Profiles에서 사용하고 있는 컴포넌트에서 Link 대신 NavLink를 사용하게 하고, 현재 선택되어 있는 경우 검정색 배경에 흰색 글씨로 스타일을 보여 주게끔 코드 수정
import React from ‘react‘;
import { NavLink, Route } from ‘react-router-dom‘;
import Profile from ‘./Profile‘;
const Profiles = () => {
const activeStyle = {
background: ‘black‘,
color: ‘white‘
};
return (
<div>
<h3>사용자 목록:</h3>
<ul>
<li>
<NavLink activeStyle={activeStyle} to=“/profiles/velopert“ active>
velopert
</NavLink>
</li>
<li>
<NavLink activeStyle={activeStyle} to=“/profiles/gildong“>
gildong
</NavLink>
</li>
</ul>
(…)
</div>
);
};
export default Profiles;