본문 바로가기

Study/npm Libraries

React-router

what is react-router?

$ npm i react-router-dom@5

 

  1. react-router vs. react-router-dom vs. react-router-native   
  2. react-router는, 클라이언트 측에서 다양한 페이지를 갖는 리액트 어플리케이션을 만들 수 있도록 도와주는 패키지이다. 
  3. react-router-dom은 브라우저에서 실행되는 리액트 어플리케이션을 위한 react-router이다.

how react-router works?

리액트 라우터를 이용한다는 것은, 서로 다른 경로가 서로 다른 컴포넌트(or page)를 렌더링하게 만든다는 것을 의미한다. 즉, SPA에서 MPA의 장점을 이용하는 것이다. 어떻게 이것이 가능할까? 

 

  1. 사용자가 새로운 url을 요청하면, 리액트 라우터가 url을 분석한다. 이 때 새로운 url이 입력되면 브라우저가 기본적으로 수행하는 일은 차단된다.
  2. 대신, 리액트 라우터가 관리하는 state를 이용해서, 그 url에 맞는 컴포넌트를 새로 렌더링한다. 
  3. 그 결과, 서로 다른 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>
  );
}

 

  1. url에 따라 렌더링 되는 컴포넌트는 pages 폴더에서 관리한다. 
  2. index에서 BrowserRouter 컴포넌트를 제공한다.
  3. => 모든 하위 컴포넌트에서 Route 컴포넌트를 이용해 path에 따라 어떤 컴포넌트를 렌더링할 것인지 결정할 수 있다. 
  4. ??? 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>
    );
};

 

  1. anchor | 링크를 클릭하면, 서버에 새로운 html 요청을 보내게 된다. 
  2. Link | 링크를 클릭했을 때 새로운 html을 요청하는 대신, 새로운 컴포넌트를 렌더링한다.이 때, props으로 href가 아니라 to를 전달한다. 
    • 내부적으로는 여전히 a 태그를 사용하고 있다.
    • 하지만, anchor를 클릭했을 때 기본적으로 브라우저에서 처리하는 행동을 무시한다. 즉, 새로운 요청을 보내지 않는다. 
  3. NavLink | 기본적으로 Link와 동일하게 동작한다.
    • 이에 더해서, 어떤 Link가 활성화(클릭) 되었을 때에만 스타일링을 지정할 수 있도록, activeClassName이라는 속성을 제공한다. 

 

anchor(좌) Link(우)

Link vs.useHistory 

import { useHistory } from 'react-router-dom';

const QuoteForm = (props) => {
  const history = useHistory();
  
  function submitHandler(event) {
    /*...*/
    history.push('/url')
  }
}

 

  1. Link | 클릭하면 페이지를 전환할 수 있다.
  2. useHistory | history 객체를 반환한다. 
  3.  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>
  );
};

 

  1. path="/product-detail/:productId" 
    • 해당 path의 productId 부분(segment)에 어떤 값이 오든, 해당하는 컴포넌트를 렌더링할 수 있다. 
    • 예를 들어 "/product-detail/ 12943afdjkhf", "/product-detail/ product1" 모두  ProductDetail 컴포넌트를 렌더링한다.
    • 하나의 url은 여러 segment를 가질 수 있다.  
  2. useParams | react-router에서 제공하는 커스텀 훅으로, 객체를 반환한다.
    • 사용자가 입력한 url 에서 segment에 해당하는 값을 추출하고, 프로퍼티의 value로 반환한다. 
    • ex) { productId : 12943afdjkhf} 
  3. regular params 
    • ex) "/:productsId"
    • url에 해당 segment가 반드시 포함되어 있어야, 해당하는 컴포넌트를 렌더링한다. 
    • useParams을 이용해 값을 추출할 수 있다. 
  4.  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 

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>
  );
};

 

  1. Without Switch | "/products/p1"으로 이동하는 Link를 클릭하면, ProductDetail 컴포넌트 뿐만 아니라, Products 컴포넌트도 함께 화면에 렌더링된다. => 이것은 리액트 라우터가 동작하는 방식 때문이다. 
    • 리액트 라우터는, 한번에 하나의 route만 load 하는 것이 아니라, 현재 path와 일치하는 모든 route를 load한다. 
    • 이 때 '일치' 여부는, 현재 경로의 시작 부분을 가지고 판단한다.
    • 예를 들어 현재 경로가  "/products/p1"일 때, 이 경로는 "/products"와 일치하는 route이다. => 따라서 두 경로에 해당하는 컴포넌트가 모두 렌더링 된다. 
    • "/products" 경로는 "/products/p1"과 일치하지 않는 경로이다. => "/products" 경로에서는 detail 컴포넌트를 보여주지 않는다. 
  2. 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 라고 이름 지은 건가
  3. 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>
    </>
  );
};

 

  1. 특정 컴포넌트 내부에서도, Route를 작성할 수 있다. 
  2. 이 때 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>
    </>
  );
};

 

  1. Prompt | 사용자가 다른 페이지로 navigate 하는 것을 감시하고, 특정 조건을 만족했을 때 warning을 보여줄 수 있다. 이 때 Prompt는 2개의 props을 필요로 한다.
  2. when | boolean을 값으로 갖는다. 
  3. message | string을 반환하는 함수를 값으로 갖는다. 이 때 함수는 사용자가 이동하려는 페이지의 위치 정보를 가지고 있는 location 객체를 인자로 갖는다. 
  4. 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