본문 바로가기
Frontend/ReactJS(완)

React - 13장 : 리액트 라우터로 SPA 개발하기

by 리키이 2023. 2. 21.

<리액트를 다루는 기술>

React - 1장 : React 이해

React - 2장 : JSX

React - 3장 : 컴포넌트

React - 4장 : 이벤트 핸들링

React - 5장 : ref. DOM에 이름 달기 

React - 6장 : 컴포넌트 반복
React - 7장 : 컴포넌트의 LifeCycle
React - 8장 : React Hooks 총정리

React - 9장 : 컴포넌트의 스타일링

React - 10장 : 빠르게 TODO앱 실습

React - 11장 : 컴포넌트 성능 최적화

React - 12장 :  immer를 사용하여 더 쉽게 불변성 유지하기

React - 13장 : 리액트 라우터로 SPA 개발하기(현재)

 

 


 

13-1. 싱글페이지 애플리케이션(SPA) 이란?

- 멀티 페이지 애플리케이션(기존 방식)

   - 다른 페이지로 이동할 때 마다 새로운 html, 페이지 로딩 시 서버에서 리소스 전달받아 해석한 뒤 화면 보여줌

   - 사용자에게 보이는 화면은 서버 측에서 준비.

   - 유동적인 html 생성해주는 템플릿 엔진 사용

   - 서버 측에서 모든 뷰를 준비한다면 성능상의 문제가 발생

   - 바뀌지 않은 부분까지 새로 불러와 불필요한 로딩이 있어 비효율적

 

- 싱글 페이지 애플리케이션 : 말 그대로 한 개의 페이지로 이루어진 애플리케이션

   - 뷰 렌더링을 사용자의 브라우저가 담당.

   - 애플리케이션을 브라우저에 불러와서 실행시킨 후 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를

     이용하여 업데이트

   - 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션을 사용할 수 있다.

 

13-2. SPA의 단점

- 앱의 규모가 커지면 자바스크립트 파일이 너무 커진다.

 - why? 페이지 로딩 시 사용자가 실제로 방문하지 않을 수 있는 페이지의 스크립트도 불러오기 때문

- 일반 크롤러가 페이지의 정보를 제대로 수집해 가지 못한다.

 - why? 브라우저에서 자바스크립트를 사용하여 라우팅을 관리하기 때문

 

13-3. 리액트 라우터 적용 및 기본 사용법

1) 프로젝트 생성 및 라이브러리 설치

 

// 프로젝트 생성 터미널에 입력
yarn create react-app router-tutorial


// react-router-dom 라이브러리 설치
yarn add react-router-dom

 

2) 프로젝트에 라우터 적용

- 프로젝트에 리액트 라우터를 적용할 때는 src/index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사용하여 감싸면 된다.

- BrowserRouterHTML5의 History API를 사용하여 페이지를 새로고침하지 않고도 주소를 변경하고, 현재 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있도록 해 준다.

 

// index.js에 입력
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
   <BrowserRouter>
    <App />
  </BrowserRouter>
);

 

3) 페이지 컴포넌트 만들기

- 처음 보여줄 Home 컴포넌트 생성, 웹 사이트를 소개하는 About 컴포넌트 생성

- Router 컴포넌트 사용해 현재 경로에 따라 다른 컴포넌트 렌더링

 

// Route 만드는 규칙

<Route path="주소규칙" component={보여 줄 컴포넌트} />

// 라우트 설정
// App.js에 입력
// Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다.

...(생략)
 return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );

 

4) Link 컴포넌트 사용하여 다른 주소로 이동하기

- 일반 웹 페이지 : a 태그 사용

- 리액트에서는 a 태그 사용하면 안된다.

   - 페이지를 전환하는 과정에서 페이지를 새로 불러오기 때문에 애플리케이션이 가지고 있는 상태들을

     모두 날려 버린다.     

   - 렌더링된 컴포넌트들도 모두 사라지고 처음부터 렌더링한다.

- Link 컴포넌트

   - LiNK 컴포넌트를 사용하면 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 상태로 HTML5 History API를 이용하여 페이지의 주소만 변경해 준다.

   - Link 컴포넌트 자체는 a 태그로 이루어져 있지만, 페이지 전환을 방지하는 기능이 내장되어 있다.

 

//App.js에 입력 

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import About from './components/About';
import Home from './components/Home';
 
const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to="/">홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
      </ul>
      <hr />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </div>
  );
};
 
export default App;

 

 

- 결과를 확인해보면 잘 나오는 것을 확인할 수 있다.

Link 연결 및 Route 연결 된 화면

 

13-4. URL 파라미터와 퀴리스트링

- 페이지 주소를 정의할 때 가끔은 유동적인 값을 전달해야 할 때도 있다.

   • 파라미터 예시: /profiles/velopert

   • 쿼리 예시: /about?details=true

- 일반적으로 파라미터는 특정 아이디 혹은 이름을 사용하여 조회할 때 사용한다.

- 쿼리는 우리가 어떤 키워드를 검색하거나 페이지에 필요한 옵션을 전달할 때 사용한다.

 

1) URL 파라미터

- URL 파라미터는 useParams라는 Hook을 사용하여 객체 형태로 조회할 수 있다.

- URL 파라미터의 이름은 라우터 설정을 할 때 Route 컴포넌트의 path props를 통해 설정한다.

- URL 파라미터는  /profiles/:username과 같이 경로에 :를 사용하여 설정한다.

- URL 파라미터가 여러 개인 경우 /profiles/:username/:field 와 같은 형태로 설정할 수 있다.

 

// Profile.js 입력

import React from 'react';
import {useParams} from 'react-router-dom';
 
const data = {
  velopert: {
    name: '김민준',
    description: '리액트를 좋아하는 개발자'
  },
  gildong: {
    name: '홍길동',
    description: '고전 소설 홍길동전의 주인공'
  }
};
 
const Profile = () => {
  const params = useParams();
  const profile = data[params.username];
  
  return (
    <div>
      <h1>사용자 프로필</h1>
      {profile ? (
          <div>
            <h2>{profile.name}</h2>
            <p>{profile.description}</p>
          </div>
      ) : (
        <p>존재하지 않는 프로필입니다.</p>
      )}
    </div>
  );
};
 
export default Profile;

 

 

2) 쿼리스트링

- 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용

- 쿼리스트링을 사용할 때는 URL파라미터와 달리 Route 컴포넌트를 사용할 때 별도로 설정해야 하는 것이 없다.

- useLocation Hook

   - pathname: 현재 주소의 경로

   - search: 맨 앞의 ? 문자를 포함한 쿼리스트링 값

   - hash: 주소의 # 문자열 뒤의 값 

   - state: 페이지로 이동할 때 임의로 넣을 수 있는 상태 값

   - key: location 객체의 고유값, 초기에는 default이며 페이지가 변경될 때마다 고유의 값이 생성됨

 

// about.jsx 입력

import React from 'react';
import {useLocation} from 'react-router-dom';
 
const About = () => {
  const location = useLocation();
  return (
    <div>
      <h1>소개</h1>
      <p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
      <p>쿼리스트링: {location.search}</p>
    </div>
  );
};
 
export default About;

 

- useSearchParams Hook 사용 예시

   - useSearchParams는 배열 타입의 값을 반환한다.

   - const [searchParams, setSearchParams] = useSearchParams();

     - 첫번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환

        - get 메서드 : 특정 쿼리파라미터를 조회 / 조회시 쿼리파라미터가 존재하지 않는다면 null로 조회 

        - set 메서드 : 특정 쿼리파라미터를 업데이트

     - 두번째 원소는 쿼리파라미터를 객체 형태로 업데이트 할 수 있는 함수를 반환

- 쿼리파라미터를 사용할 때 주의할 점

   - 쿼리파라미터를 조회할 때 값은 무조건 문자열 타입

   - true, false 값을 넣는 다면 값을 비교할 때 'true' 와 같이 따옴표로 감사서 비교해야 한다.

   - 숫자를 다룬다면 parseInt를 사용하여 숫자타입으로 변환을 해야 한다.

 

import React from 'react';
import {useSearchParams} from 'react-router-dom';
 
const About = () => {
 const [searchParams, setSearchParams] = useSearchParams();
 const detail = searchParams.get('detail');
 const mode = searchParams.get('mode');

const onToggleDetail = () => {
  setSearchParams({mode, detail: detail ==='true' ? false : true})
}
const onIncreaseMode = () => {
  const nextMode = mode === null ? 1 : parseInt(mode) + 1 ;
  setSearchParams({mode: nextMode, detail})
}
  return (
    <div>
      <h1>소개</h1>
      <p>이 프로젝트는 리액트 라우터 기초를 실습해 보는 예제 프로젝트입니다.</p>
      <p>detail : {detail}</p>
      <p>mode : {mode}</p>
      <button onClick={onToggleDetail}>Toggle detail</button>
      <button onClick={onIncreaseMode}>mode + 1</button>
    </div>
  );
};
 
export default About;

 

13-5. 중첩된 라우트

- 라우트 컴포넌트 자식으로 중첩되게 라우트를 설정해준다.

- 부모 라우트 컴포넌트로 들어가 Outlet 이라는 컴포넌트를 사용하면 중첩된 라우트가 보여지게된다.

- article,articles 컴포넌트를 만들어주고 App컴포넌트를 수정한다.

- 중첩된 라우트를 사용하는 방식을 사용하면 컴포넌트를 한번만 사용해도 된다는 장점이 있다.

 

// Article.js
import { useParams } from 'react-router-dom';

const Article = () => {
    const { id } = useParams();
    return (
        <div>
            <h2>게시글 {id}</h2>
        </div>
    );
};

export default Article;


// Articles.js
import { Link, Outlet } from 'react-router-dom';

const Articles = () => {
    return (
        <div>
            // Route의 children으로 들어가는 jsx엘리먼트를 보여주는 역할
            <Outlet />
            <ul>
                <li>
                    <Link to="/articles/1">게시글 1</Link>
                </li>
                <li>
                    <Link to="/articles/2">게시글 2</Link>
                </li>
                <li>
                    <Link to="/articles/3">게시글 3</Link>
                </li>
            </ul>
        </div>
    );
};

export default Articles;


// App.js
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import Profile from './pages/Profile';
import Article from './pages/Article';
import Articles from './pages/Articles';
const App = () => {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/">홈</Link>
                </li>
                <li>
                    <Link to="/about">소개</Link>
                </li>
                <li>
                    <Link to="/profiles/velopert">velopert의 프로필</Link>
                </li>
                <li>
                    <Link to="/profiles/gildong">gildong의 프로필</Link>
                </li>
                <li>
                    <Link to="/profiles/void">존재하지 않는 프로필</Link>
                </li>
                <li>
                    <Link to="/articles">게시판 목록</Link>
                </li>
            </ul>
            <hr />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
                <Route path="/profiles/:username" element={<Profile />} />
                <Route path="/articles" element={<Articles />}>
                    <Route path=":id" element={<Article />} />
                </Route>
            </Routes>
        </div>
    );
};

export default App;

 

1) index props

- Route 컴포넌트에는 index라는 props가 있다. 이 props는 path="/"와 동일한 의미를 가진다.

 

 <Route index element={<Home />} />

 

13-6. 리액트 라우터 부가 기능

1) useNavigate

- Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야 하는 상황에 사용하는 Hook

- navigate(-1)을 하면 뒤로 한 번 가고 navigate(-2)를 하면 뒤로 두 번 간다. 반대로 navigate(1)은 앞으로 한 번 물론 뒤로가기를 한번 한 상태여야 한다.

- replace라는 옵션은 페이지를 이동할때 현재 페이지를 페이지 기록에 남기지 않는다.

const navigate = useNavigate();

const goBack = () => {
    // 이전 페이지로 이동
    navigate(-1);
};
const goArticles = () => {
    // articles 경로로 이동
    navigate('/articles', {replace: true});
};

...(생략)

 <button onClick={goBack}>뒤로가기</button>
 <button onClick={goArticles}>게시글 목록</button>

 

2) NavLink

- NavLink 컴포넌트는 링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트이다.

- style과 className은 {isActive : boolean} 을 파라미터로 전달받는 함수 타입의 값을 전달한다.

 


const activeStyle = {
 color: 'green',
 fontSize: 21,
};

<NavLink style={({isActive}) => isActive ? activeStyle : undefined
		 className={({isActive}) => isActive ? 'active' : undefined
}
/>

 

- NavLink 리팩터링

 

<리팩터링 전>
 <ul>
    <li>
        <Link to="/articles/1">게시글 1</Link>
    </li>
    <li>
        <Link to="/articles/2">게시글 2</Link>
    </li>
    <li>
        <Link to="/articles/3">게시글 3</Link>
    </li>
</ul>


<리팩터링 후>
 <ul>
   <ArticleItem id={1} />
   <ArticleItem id={2} />
   <ArticleItem id={3} />
</ul>
const ArticleItem = ({ id }) => {
    const activeStyle = {
        color: 'green',
        fontSize: 21,
    };
    return (
        <li>
            <NavLink to={`/articles/${id}`} style={({ isActive }) => (isActive ? activeStyle : undefined)}>
                게시글 {id}
            </NavLink>
        </li>
    );
};

 

3) NotFound

- 사전에 정의되지 않는 경로로 사용자가 진입했을 때 보여주는 페이지

- *는 wildcard 문자인데 아무 텍스트나 매칭한다는 뜻이다.

- 일치하는 라우트가 없다면 이 라우트가 화면에 나타난다.

 

//NotFound.js
const NotFound = () => {
    return <div>404</div>;
};

export default NotFound;



//App.js
<Route path="*" element={<NotFound />} />

 

4) Navigate 컴포넌트

- Navigate 컴포넌트는 컴포넌트를 화면에 보여주는 순간 다른 페이지로 이동을 하고 싶을 때 사용

- 페이지를 리다이렉트하고 싶을 때 사용

- 예컨데 사용자의 로그인이 필요한 페이지인데 로그인을 안 했다면 로그인 페이지를 보여줘야 될때

- isLoggedIn 값이 false이기 때문에 Navigate 컴포넌트를 통해 /login 경로로 이동한다.

 

// Login.js

const Login = () => {
    return <div>로그인 페이지</div>;
};

export default Login;


// MyPage.js

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

const MyPage = () => {
    const isLoggedIn = false;
    if (!isLoggedIn) {
        return <Navigate to="/login" replace={true} />;
    }

    return <div>마이 페이지</div>;
};

export default MyPage;


//App.js
import MyPage from './pages/MyPage';
import Login from './pages/Login';



...(생략)
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />

댓글