즉 보이는 화면은 서버에서 준비했음
요즘은 웹에서 제공 정보 매우 많기 때문에 서버가 모든 뷰 준비하면 부하 가능성
따라서 리액트 같은 라이브러리나 프레임워크 사용해 뷰 렌더링을 사용자의 브라우저가 담당하게 하고 인터랙션 발생시 필요한 부분만 js 사용해 업데이트 해줌
새 데이터 필요시 서버 API 호출해 필요만 데이터만 새로 불러와 사용
싱글 페이지라고 해서 화면 한 종류만인것은 아님
SPA의 경우 서버에서 제공하는 페이지는 한 종류이지만 해당 페이지에서 로딩된 js와 현 사용자 브라우저 주소 상태에 따라 다양한 화면 보여줄 수 있음
리액트 라우팅 라이브러리로 리액트 라우터(react-router), 리치 라우터(reach-router), Next.js 등이 있다
리액트 라우터는 클라이언트 사이드에서 이뤄지는 라우팅 아주 간단하게 구현 도와줌
서버 사이드 렌더링 돕는 컴포넌트도 제공
리액트 라우터처럼 브라우저에서 js 사용해 라우팅 관리하는 것은 js 실행않는 일반 크롤러에서는 페이지 정보 제대로 수집 못한다는 단점
=> 서버 사이드 렌더링 통해 해결 가능
프로젝트에 리액트 라우터 적용시에는 BrowserRouter 컴포넌트 사용해 감싸면 됨
이 컴포넌트는 웹 앱에 html5의 history api를 사용해 페이지를 새로고침 하지 않고도 주소 변경하고
현 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있게 해줌
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById(‘root‘)
);
다음과 같은 식으로 사용한다.
<Route path = '주소 규칙' component = {보여줄 컴포넌트} />
주소에 따라 다른 컴포넌트 보여줌
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} /> {/*맨 처음에는 Home 컴포넌트 보여줌*/}
<Route path = '/about' component = {About} /> {/* /about 주소에서는 About 컴포넌트 보여줌*/}
</div>
)
}
/about 경로를 들어가면 About 컴포넌트만 나오는것이 아니라 두 컴포넌트 모두 나타난다.
/about 경로가 / 규칙에도 일치하기 때문
이를 수정하려면 Home을 위한 Route 컴포넌트를 사용할 때 exact 라는 props를 true로 설정하면 됨
<Route path = '/' component = {Home} exact = {true}/>
일반 웹 앱에서는 a 태그 사용해 페이지 전환
but 리액트 라우터 사용시에는 a 태그 직접 사용하면 안됨
a 태그는 페이지 전환 과정에서 페이지를 새로 불러오기 때문에 앱이 들고있언 상태들을 모두 날려 버림
즉 렌더링된 컴포넌트들도 모두 사라지고 다시 처음부터 렌더링 해야 함
Link 컴포넌트 사용해 페이지 전환하면 페이지 새로 불러오지 않고 앱은 그대로 유지한 상태에서 html5 history API 사용해 페이지의 주소만 변경해줌
Link 컴포넌트 자체는 a 태그로 이뤄져 있지만 페이지 전환 방지 기능 내장되어 있음.
다음과 같은 식으로 사용한다.
<Link to = '주소'>내용</Link>
App 컴포넌트에서 '/', '/about' 경로로 이동하는 Link 컴포넌트
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}/> {/*맨 처음에는 Home 컴포넌트 보여줌*/}
<Route path = '/about' component = {About} /> {/* /about 주소에서는 About 컴포넌트 보여줌*/}
</div>
)
}
export default App;
링크를 눌러 페이지를 전환할 수 있다.
<Route path = '/about' component = {About} />
<Route path = '/info' component = {About} />
그 대신 path props를 배열로 설정해주면 여러 경로에서 같은 컴포넌트를 보여 줄 수 있다.
<Route path = {['/about', '/info']} component={About} /> {/*배열로 설정*/}
유동적인 값을 사용할 때 무엇을 사용할 지 규칙은 없지만
일반적으로 아이디나 이름 사용하여 조회시 파라미터, 키워드 검색이나 페이지에 필요힌 옵션 전달 시 쿼리를 사용한다.
// Profile.js
import React from 'react';
const data = {
tom : {
name: Thomas,
description: '리액트 개발자'
},
gildong : {
name: '홍길동',
description: '홍길동전 주인공'
}
};
// url 파라미터 사용시 라우트로 사용되는 컴포넌트에서
// 받아오는 match라는 객체 안의 params값을 참조.
// match 객체 안에는 현재 컴포넌트가 어떤 경로 규칙에
// 의해 보이는지에 대한 정보 들어있음.
const Profile = ({match}) => {
const {username} = match.params;
const profile = data[username];
if (!profile) {
return <div>존재하지 않는 사용자입니다.</div>;
}
return (
<div>
<h2>
{username}({profile.name})
</h2>
<p>{profile.description}</p>
</div>
)
}
export default Profile;
이제 App 컴포넌트에서 Profile 컴포넌트를 위한 라우트를 정의.
이번에 사용할 path 규칙은 /profiles/:username
이렇게 설정하면 match.params.username 값을 통해 현재 username 값을 조회할 수 있다.
// App.js
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/tom'>tom 프로필</Link>
</li>
<li>
<Link to = '/profile/gildong'>gildong 프로필</Link>
</li>
</ul>
<hr />
<Route path = '/' component = {Home} exact = {true}/> {/*맨 처음에는 Home 컴포넌트 보여줌*/}
<Route path = {['/about', '/info']} component={About} /> {/*배열로 설정*/}
<Route path = '/profile/:username' component={Profile} /> {/*match.params.username 값을 통해 현재 username 값 조회 가능*/}
</div>
)
}
export default App;
location은 다음과 같은 형태.
{
'pathname': './about',
'search': '?detail=true',
'hash': ' '
}
위 location객체는 http://localhost:3000/about?detail=true 주소로 들어갔을 때의 값.
url 쿼리를 읽을때 위 객체에서 search값을 확인해야 한다.
문자열로 이뤄져있고 '?detail=true&another=1' 처럼 여러가지 값을 설정할 수 있다.
search에서 특정 값을 읽어오려면 문자열을 객체로 변환해줘야 한다.
쿼리 문자열 객체로 변환하는데 qs 라이브러리가 쓰인다.
import React from 'react';
import qs from 'qs';
// About.js
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>
)
}
// 브라우저에서 http://localhost:3000/about?detail=true 주소로 들어가면 위 문장 출력됨.
export default About;
쿼리 문자열을 객체로 파싱하는 과정에서 결과값은 언제나 문자열이라는 점 주의하자.
?value=1 이나 ?value=true 처럼 숫자나 bool도 문자열로 받아짐.
import React from 'react';
import {Link, Route} from 'react-router-dom';
import Profile from './Profile';
// Profiles.js
const Profiles = () => {
return (
<div>
<h3>사용자 목록:</h3>
<ul>
<li><Link to = '/profiles/tom'>톰</Link></li>
<li><Link to = '/profiles/gildong'>길동</Link></li>
</ul>
{/* component 대신 render라는 props 넣었다. 컴포넌트 자체 전달하는것이 아니라
보여주고 싶은 jsx 넣을 수 있다. jsx에서 props 설정 시 값 생략하면 자동으로 true 설정됨
현재 exact={true} 대신 exact만 써도 같은 의미 */}
<Route path = '/profiles' exact render = {() => <div>사용자를 선택</div>} />
<Route path = '/profiles/:username' component={Profile} />
</div>
)
}
export default Profiles;
// App.js
...
<li><Link to = '/profiles'>프로필</Link></li>
...
<Route path = '/profiles' component={Profiles} />
history를 사용하는 페이지를 만들어보자.
import React, {Component} from 'react';
// HistorySample.js
class HistorySample extends Component {
// 뒤로 가기
handleGoBack = () => {
this.props.history.goBack();
};
// 홈으로 이동
handleGoHome = () => {
this.props.history.push('/');
};
componentDodMount() {
// 페이지에 변화 생길때마다 정말 나갈것인지 질문
this.unblock = this.props.history.block('Are you sure?');
};
componentWillUnmount() {
// 컴포넌트가 언마운트되면 질문 멈춤
if (this.unblock)
this.unblock();
}
render() {
return (
<div>
<button onClick = {this.handleGoBack}>뒤로</button>
<button onClick = {this.handleGoHome}>홈으로</button>
</div>
);
}
}
export default HistorySample;
App에서 /history 경로에 보이도록 설정.
...
import HistorySample from './HistorySample';
...
<li><Link to = '/history'>History 예제</Link></li>
...
<Route path = '/history' component={HistorySample} />
import React from "react";
import { withRouter } from "react-router-dom";
// WithRouterSample.js
const WithRouterSample = (({location, match, history}) => {
return (
<div>
<h4>location</h4>
<textarea value={JSON.stringify(location, null, 2)}
rows={7}
readOnly={true} />
<button onClick = {() => history.push('/')}>홈으로</button>
</div>
)
})
// withRouter 사용할때는 컴포넌트 내보낼 때 함수로 감싸 줌
export default withRouter(WithRouterSample);
이 컴포넌트를 Profiles 컴포넌트에 렌더링 해보자.
// Profiles.js
...
<div>
<WithRouterSample />
...
그런데 match의 params 가 비어있다.
withRouter 는 현재 자신을 보여주고 있는 라우트 컴포넌트(현재 Profiles)를 기준으로 match가 전달됨.
Profiles를 위한 라우트를 설정할때 path="/profiles" 라고만 입력했으므로 username 파라미터를 읽어오지 목하는 상태.
WIthRouterSample 컴포넌트를 Profiles에서 지우고 Profile에 넣으면 match에 url 파라미터가 제대로 전달됨.
// Profile.js
...
<div>
<WithRouterSample />
...
params.username이 제대로 출력된다.
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';
// App.js
const App = () => {
return (
<div>
<ul>
<li><Link to = '/'>홈!</Link></li>
<li><Link to = '/about'>about!</Link></li>
<li><Link to = '/about2'>about2!</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', '/about2']} component={About} />
<Route path = '/profiles' component={Profiles} />
<Route path = '/history' component={HistorySample} />
<Route
render={({location}) => (
<div>
<h2>이 페이지는 존재하지 않습니다.</h2>
<p>{location.pathname}</p>
</div>
)} />
</Switch>
</div>
)
}
export default App;
위에서도 사용자가 /about 들어왔을때 당장 필요하지 않은 Profile 컴포넌트까지 불러옴.
다른 컴포넌드는 필요한 시점에만 불러오는것이 효율적.
=> 코드 스플리팅 통해 해결 가능