what is react-router?
$ npm i react-router-dom@5
- react-router vs. react-router-dom vs. react-router-native
- react-router는, 클라이언트 측에서 다양한 페이지를 갖는 리액트 어플리케이션을 만들 수 있도록 도와주는 패키지이다.
- react-router-dom은 브라우저에서 실행되는 리액트 어플리케이션을 위한 react-router이다.
how react-router works?
리액트 라우터를 이용한다는 것은, 서로 다른 경로가 서로 다른 컴포넌트(or page)를 렌더링하게 만든다는 것을 의미한다. 즉, SPA에서 MPA의 장점을 이용하는 것이다. 어떻게 이것이 가능할까?
- 사용자가 새로운 url을 요청하면, 리액트 라우터가 url을 분석한다. 이 때 새로운 url이 입력되면 브라우저가 기본적으로 수행하는 일은 차단된다.
- 대신, 리액트 라우터가 관리하는 state를 이용해서, 그 url에 맞는 컴포넌트를 새로 렌더링한다.
- 그 결과, 서로 다른 url이 서로 다른 페이지를 보여주는 것 처럼 어플리케이션을 만들 수 있다.
how to use react-router?
BrowserRoute & Route
// index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
// app.js
import { Route } from 'react-router-dom';
function App() {
return (
<div>
<Route path='/welcome'>
<Welcome />
</Route>
<Route path='/products'>
<Products />
</Route>
</div>
);
}
- url에 따라 렌더링 되는 컴포넌트는 pages 폴더에서 관리한다.
- index에서 BrowserRouter 컴포넌트를 제공한다.
- => 모든 하위 컴포넌트에서 Route 컴포넌트를 이용해 path에 따라 어떤 컴포넌트를 렌더링할 것인지 결정할 수 있다.
- ??? url에 /welcome 이나 /products를 입력하면, 새로운 요청을 보낸다.
a vs. Link vs. NavLink
// 1. anchor
const Header = (props) => {
return (
// header > nav > ul
<li>
<a href='/welcome'>Welcome</a>
</li>
<li>
<a href='/products'>Products</a>
</li>
);
};
// 2. Link
import { Link } from 'react-router-dom';
const Header = (props) => {
return (
<li>
<Link to='/welcome'>Welcome</Link>
</li>
<li>
<Link to='/products'>Products</Link>
</li>
);
};
// 3. NavLink
import { NavLink } from 'react-router-dom';
const Header = (props) => {
return (
<li>
<NavLink activeClassName={styles.active} to='/welcome'>Welcome</NavLink>
</li>
<li>
<NavLink activeClassName={styles.active} to='/products'>Products</NavLink>
</li>
);
};
- anchor | 링크를 클릭하면, 서버에 새로운 html 요청을 보내게 된다.
- Link | 링크를 클릭했을 때 새로운 html을 요청하는 대신, 새로운 컴포넌트를 렌더링한다.이 때, props으로 href가 아니라 to를 전달한다.
- 내부적으로는 여전히 a 태그를 사용하고 있다.
- 하지만, anchor를 클릭했을 때 기본적으로 브라우저에서 처리하는 행동을 무시한다. 즉, 새로운 요청을 보내지 않는다.
- NavLink | 기본적으로 Link와 동일하게 동작한다.
- 이에 더해서, 어떤 Link가 활성화(클릭) 되었을 때에만 스타일링을 지정할 수 있도록, activeClassName이라는 속성을 제공한다.
Link vs.useHistory
import { useHistory } from 'react-router-dom';
const QuoteForm = (props) => {
const history = useHistory();
function submitHandler(event) {
/*...*/
history.push('/url')
}
}
- Link | 클릭하면 페이지를 전환할 수 있다.
- useHistory | history 객체를 반환한다.
- 특정 action이 발생했을 때 페이지를 전환할 수 있다. => 예를 들어 form을 제출하면, main 페이지로 이동할 수 있다.
- useHistory를 이용하면 브라우저 history(사용자가 방문한 페이지 리스트)를 변경할 수 있다.
- how to change histroy?
- push | page stack(쌓아올림 => LIFO)에 새로운 page를 push한다.
- replace | ??? redirect 처럼 current page를 교체한다.
- push는 이전에 방문한 페이지의 기록을 그대로 남기는 반면, replace는 삭제한다.
params | dynamic url
// app.js
<Route path='/product-detail/:productId'>
<ProductDetail />
</Route>
// ProductDetail.jsx
import {useParams} from 'react-router-dom'
const ProductDetail = (props) => {
const params = useParams();
return (
<section>
<h1>{params.productId} Details</h1>
<p> this is ~ </p>
</section>
);
};
- path="/product-detail/:productId"
- 해당 path의 productId 부분(segment)에 어떤 값이 오든, 해당하는 컴포넌트를 렌더링할 수 있다.
- 예를 들어 "/product-detail/ 12943afdjkhf", "/product-detail/ product1" 모두 ProductDetail 컴포넌트를 렌더링한다.
- 하나의 url은 여러 segment를 가질 수 있다.
- useParams | react-router에서 제공하는 커스텀 훅으로, 객체를 반환한다.
- 사용자가 입력한 url 에서 segment에 해당하는 값을 추출하고, 프로퍼티의 value로 반환한다.
- ex) { productId : 12943afdjkhf}
- regular params
- ex) "/:productsId"
- url에 해당 segment가 반드시 포함되어 있어야, 해당하는 컴포넌트를 렌더링한다.
- useParams을 이용해 값을 추출할 수 있다.
- query params
- ex) "/quotes?sort=value"
- value를 동적으로 설정해서 history.push와 함께 이용하면, 정렬 방법에 따라 다른 페이지를 보여줄 수 있다.
- url에 선택적으로 포함할 수 있는 추가 정보(extra information)이다.
- 즉, url의 query params 포함 여부가 Route에 영향을 주지 않는다.
- query params은 url의 가장 끝 부분에 작성할 수 있다.
- useLocation을 이용해 값을 추출할 수 있다.
- new URLSearchParams() | 브라우저 내장 함수, string을 전달하면 객체({sort: "value"})로 변환해 준다.
- useLocation().search | string
- ex) "/quotes?sort=value"
Switch & exact
// app.js
import { Switch } from 'react-router-dom';
function App() {
return (
// <Container>
<Switch>
<Route path='/products' exact>
<Products />
</Route>
<Route path='/products/:productId'>
<ProductDetail />
</Route>
<Route path='*'>
<NotFound />
</Route>
</Switch>
// </Container>
);
}
// Products.jsx
const Products = (props) => {
return (
<section>
<h1>products page</h1>
<ul>
<li>
<Link to='/products/p1'>Grape Juice</Link>
</li>
<li>
<Link to='/products/p2'>Message in a bottle</Link>
</li>
</ul>
</section>
);
};
// ProductDetail.jsx
const ProductDetail = (props) => {
const params = useParams(); // return object
return (
<section>
<h1>{params.productId} Details</h1>
</section>
);
};
- Without Switch | "/products/p1"으로 이동하는 Link를 클릭하면, ProductDetail 컴포넌트 뿐만 아니라, Products 컴포넌트도 함께 화면에 렌더링된다. => 이것은 리액트 라우터가 동작하는 방식 때문이다.
- 리액트 라우터는, 한번에 하나의 route만 load 하는 것이 아니라, 현재 path와 일치하는 모든 route를 load한다.
- 이 때 '일치' 여부는, 현재 경로의 시작 부분을 가지고 판단한다.
- 예를 들어 현재 경로가 "/products/p1"일 때, 이 경로는 "/products"와 일치하는 route이다. => 따라서 두 경로에 해당하는 컴포넌트가 모두 렌더링 된다.
- "/products" 경로는 "/products/p1"과 일치하지 않는 경로이다. => "/products" 경로에서는 detail 컴포넌트를 보여주지 않는다.
- Switch | route를 switch 컴포넌트로 감싸면, 한 번에 하나의 route만 반환한다.
- 사용자가 특정 url을 입력하면, 리액트 라우터는 switch 컴포넌트의 route를 순서대로 분석한다.
- 사용자가 입력한 url과 '일치하는' route를 찾으면, 그 route를 반환하고 남은 route는 분석하지 않는다.
- => 따라서 "/products/:productsId" route를 "/products" route 보다 먼저 정의해야 한다.
- => 또는 "/products" route에 exact props을 전달한다.
- 일치하는 route가 존재하지 않으면, 화면에 아무것도 렌더링하지 않는다.
- => 마지막 route에 wild card(*) path를 전달해서 not found page 컴포넌트를 작성할 수 있다.
- ??? switch 구문이랑 비슷해서 Switch 라고 이름 지은 건가
- Container | switch를 스타일링을 위한 레이아웃 컴포넌트로 감쌀 수 있다.
nested Route
import { Route } from 'react-router-dom';
const Welcome = (props) => {
return (
<section>
<h1>welcome page</h1>
<Route path='/welcome/new-user'>
<h1>welcome, new user!</h1>
</Route>
</section>
);
};
// QuoteDetails("/quotes/:id")
const QuoteDetails = (props) => {
const params = useParams();
return (
<>
<HighlightedQuote
author={targetQuote.author}
text={targetQuote.text}
/>
// Route 안쓰고 Link만 쓰면 되지 않나?
<Route path={`/quotes/${params.quoteId}`} exact>
<Link to={`/quotes/${params.quoteId}/comments`}>
load comments
</Link>
</Route>
<Route path={`/quotes/${params.quoteId}/comments`}>
<Comments />
<CommentsList comments={commentsCtx.comments} />
</Route>
</>
);
};
- 특정 컴포넌트 내부에서도, Route를 작성할 수 있다.
- 이 때 route의 path는 그 컴포넌트에 해당하는 path로 시작해야 한다. 그렇지 않으면 해당 경로는 절대 활성화 되지 않는다.
Redirect
import { Route, Switch, Redirect } from 'react-router-dom';
function App() {
return (
<Switch>
<Route path='/' exact>
<Redirect to='/welcome' />
</Route>
<Route path='/welcome'>
<Welcome />
</Route>
</Switch>
);
}
- 사용자가 "/" path를 입력했을 때, "/welcome" 페이지로 이동한다.
Prompt
사용자가 form에 값을 입력하기 시작하면(form active => onFocus), 실수로 form 페이지를 벗어나는 일이 없도록 prompt 창을 띄워줄 수 있다.
import { Prompt } from 'react-router-dom';
const QuoteForm = (props) => {
const [isFocused, setFocus] = useState(false);
const formFocusHandler = () => {
setFocus(true);
};
return (
<>
<Prompt
when={isFocused}
message={(location) =>
'Are you sure to leave this page? Your form data will be lost.'
}
/>
<form onFocus={formFocusHandler}>
/*...*/
<button
type='submit'
onClick={() => setFocus(false)}
/>
</form>
</>
);
};
- Prompt | 사용자가 다른 페이지로 navigate 하는 것을 감시하고, 특정 조건을 만족했을 때 warning을 보여줄 수 있다. 이 때 Prompt는 2개의 props을 필요로 한다.
- when | boolean을 값으로 갖는다.
- message | string을 반환하는 함수를 값으로 갖는다. 이 때 함수는 사용자가 이동하려는 페이지의 위치 정보를 가지고 있는 location 객체를 인자로 갖는다.
- form을 제출할 때에는 해당 propmt가 나타나지 않도록 button에 click 이벤트를 등록한다(submit event❌).
useRouteMatch
const QuoteDetails = (props) => {
const match = useRouteMatch();
return (
// ...
//<Route path={`/quotes/${params.quoteId}/comments`}>
<>
<Route path={`${match.path}/comments`}>
<Comments />
<CommentsList comments={commentsCtx.comments} />
</Route>
</>
);
};
- 객체를 반환한다.
- nested route를 정의할 때, path를 처음부터 끝까지 하드 코딩하지 않고, hook을 통해 필요한 부분을 받아올 수 있다.
'Study > npm Libraries' 카테고리의 다른 글
React with web accessibility (0) | 2022.09.19 |
---|---|
TanStack Query(React Query) (0) | 2022.07.24 |
React-router(v6) (0) | 2022.07.03 |
React-redux & Redux-toolkit (0) | 2022.06.30 |
what is Redux? (0) | 2022.06.29 |