본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024 React.js 스터디] 박지민 #마지막주차 "API 연동과 라우터"

반응형

1. API 연동의 기본

- API 를 호출하기 위해서 axios 라는 라이브러리를 설치

yarn add axios

 

- axios를 사용해서 GET, PUT, POST, DELETE 등의 메서드로 API 요청을 할 수 있음

  • GET: 데이터 조회
  • POST: 데이터 등록
  • PUT: 데이터 수정
  • DELETE: 데이터 제거
import axios from 'axios';

axios.get('/users/1');

- get 이 위치한 자리에는 메서드 이름을 소문자로 넣음 - ex) 새로운 데이터를 등록하고 싶다면 axios.post() 를 사용

 

- 파라미터에는 API 의 주소를 넣음

- axios.post() 로 데이터를 등록 할 때에는 두번째 파라미터에 등록하고자 하는 정보를 넣을 수 있음

axios.post('/users', {
  username: 'blabla',
  name: 'blabla'
});

 

- 우리가 이번에 API 연동 실습을 할 때에는 JSONPlaceholder 에 있는 연습용 API 를 사용할 것

 

useState 와 useEffect 로 데이터 로딩하기

- useState 를 사용하여 요청 상태를 관리 & useEffect 를 사용하여 컴포넌트가 렌더링되는 시점에 요청을 시작

- 요청에 대한 상태를 관리 할 때에는 다음과 같이 총 3가지 상태를 관리

  1. 요청의 결과
  2. 로딩 상태
  3. 에러
// Users.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;  // 로딩 상태가 활성화 됐을 때
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;  // users 값이 아직 없을 때
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;

- useEffect 에 첫번째 파라미터로 등록하는 함수에는 async 를 사용 할 수 없기 때문에 함수 내부에서 async 를 사용하는 새로운 함수를 선언해주어야 함

- 맨 마지막에서는 users 배열을 렌더링하는 작업을 해줌

 

// App.js
import React from 'react';
import Users from './Users';

function App() {
  return <Users />;
}

export default App;

 

버튼을 눌러서 API 재요청하기

- 아까 구현했던 fetchUsers 함수를 바깥으로 꺼내주고, 버튼을 만들어서 해당 함수를 연결

// Users.js
function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    try {
      // 요청이 시작 할 때에는 error 와 users 를 초기화하고
      setError(null);
      setUsers(null);
      // loading 상태를 true 로 바꿉니다.
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

 

2. React Router v6 튜토리얼

라우팅이란?

- 웹 애플리케이션에서 라우팅이라는 개념은 사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것을 의미

- 웹 애플리케이션을 만들때 프로젝트를 하나의 페이지로 구성할 수도 있고, 여러 페이지를 구성할 수 있음

ex) 로그를 만든다고 가정

  • 글쓰기 페이지: 새로운 포스트를 작성하는 페이지
  • 포스트 목록 페이지: 블로그에 작성된 여러 포스트들의 목록을 보여주는 페이지
  • 포스트 읽기 페이지: 하나의 포스트를 보여주는 페이지

페이지별로 컴포넌트들을 분리해가면서 프로젝트를 관리하기 위해 필요한 것이 바로 라우팅 시스템

 

- 리액트에서 라우트 시스템을 구축하기위해 사용할 수 있는 선택지는 크게 두가지

  1. 리액트 라우터 (React Router): 리액트의 라우팅 관련 라이브러리들 중에서 가장 오래됐고, 가장 많이 사용 - 라이브러리는 컴포넌트 기반으로 라우팅 시스템을 설정할 수 있음
  2. Next.js: 리액트 프로젝트의 프레임워크 - 우리가 사용했던 Create React App처럼 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능들을 제공 - 이 프레임워크의 라우팅 시스템은 파일 경로 기반으로 작동 - 이 프레임워크는 리액트 라우터의 대안으로 많이 사용

- 라우팅 관련 기능을 리액트 라이브러리에서 공식적으로 지원하는 것이 아니라 써드 파티로서 제공 -> 이 외에도 react-location, rakkas 등의 프로젝트들이 존재

 

싱글 페이지 애플리케이션이란?

- 한 개의 페이지로 이루어진 애플리케이션이라는 의미

 

- 먼저  멀티 페이지 애플리케이션은 어떻게 작동하는지 알아야 함

멀티 페이지 애플리케이션에서는 사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 리소스를 전달받아 브라우저 화면에 보여줌

각 페이지마다 다른 html 파일을 만들어서 제공을 하거나, 데이터에 따라 유동적인 html을 생성해 주는 템플릿 엔진을 사용

=> 사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 이 방식이 적합 X

 

-> 리액트 같은 라이브러리를 사용해서 뷰 렌더링을 사용자의 브라우저가 담당하도록 하고, 우선 웹 애플리케이션을 브라우저에 불러와서 실행시킨 후에 사용자와의 인터랙션이 발생하면 필요한 부분만 자바스크립트를 사용하여 업데이트하는 방식을 사용 - 만약 새로운 데이터가 필요하다면 서버 API를 호출하여 필요한 데이터만 새로 불러와 애플리케이션에서 사용

 

- 이렇게 html은 한번만 받아와서 웹 애플리케이션을 실행시킨 후에 그 이후에는 필요한 데이터만 받아와서 화면에 업데이트 해주는 것이 싱글 페이지 애플리케이션

- 기술적으로는 한 페이지만 존재하는 것이지만, 사용자가 경험하기에는 여러 페이지가 존재하는 것 처럼 느껴짐

 

리액트 라우터 적용 및 기본 사용법

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

- 라우터를 설치할 때는 yarn 을 사용하여 react-router-dom이라는 라이브러리를 설치

yarn add react-router-dom

 

프로젝트에 라우터 적용

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

- 이 컴포넌트는 웹 애플리케이션에 HTML5의 History API를 사용하여 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소의 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해줌

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

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

 

페이지 컴포넌트 만들기

- 리액트 라우터를 통해 여러 페이지로 구성된 웹 애플리케이션을 만들기 위하여 각 페이지에서 사용할 컴포넌트를 만듦

- 사용자가 웹 사이트에 들어오게 됐을 때 가장 먼저 보여지게 될 Home 페이지 컴포넌트와 웹 사이트를 소개하는 About 페이지 컴포넌트

// src/pages/Home.js
const Home = () => {
  return (
    <div>
      <h1>홈</h1>
      <p>가장 먼저 보여지는 페이지입니다.</p>
    </div>
  );
};

export default Home;
// src/pages/About.js
const About = () => {
  return (
    <div>
      <h1>소개</h1>
      <p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
    </div>
  );
};

export default About;

- 이 컴포넌트들을 꼭 pages 경로에 넣을 필요 X -  단순히 페이지를 위한 컴포넌트들을 다른 파일들과 구분하기 위함

 

Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기

- 사용자의 브라우저 주소 경로에 따라 우리가 원하는 컴포넌트를 보여주기 위해서 Route 라는 컴포넌트를 통해 라우트 설정

- Route 컴포넌는 다음과 같이 사용

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

- Route 컴포넌트는 Routes 컴포넌트 내부에서 사용

// src/App.js
import { Route, Routes } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
};

export default App;

 

Link 컴포넌트를 사용하여 다른 페이지로 이동하는 링크 보여주기

- 웹 페이지에서는 원래 링크를 보여줄 때 a 태그를 사용 but 리액트 라우터를 사용하는 프로젝트에서 a 태그를 바로 사용 X: a 태그를 클릭하여 페이지를 이동할 때 브라우저에서는 페이지를 새로 불러오게 되기 때문

- Link 컴포넌트 역시 a 태그를 사용하긴 하지만, 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있음

- Link  컴포넌트는 다음과 같이 사용합니다.

<Link to="경로">링크 이름</Link>

- Home 페이지에서 About 페이지로 이동할 수 있도록 Link 컴포넌트를 Home 페이지 컴포넌트에서 사용

// src/pages/Home.js
import { Link } from 'react-router-dom';

const Home = () => {
  return (
    <div>
      <h1>홈</h1>
      <p>가장 먼저 보여지는 페이지입니다.</p>
      <Link to="/about">소개</Link>
    </div>
  );
};

export default Home;

 

URL 파라미터와 쿼리스트링

- 페이지 주소를 정의할 때 가끔은 유동적인 값을 사용해야 할 때도 있음

  • URL 파라미터 예시: /profile/velopert
  • 쿼리스트링 예시: /articles?**page=1&keyword=react

- URL 파라미터는 주소의 경로에 유동적인 값을 넣는 형태고, 쿼리 스트링은 주소의 뒷부분에 ? 문자열 이후에 key=value 로 값을 정의하며 & 로 구분을 하는 형태

- 주로 URL 파라미터는 ID 또는 이름을 사용하여 특정 데이터를 조회할 때 사용하고, 쿼리스트링(Querystring)은 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용

 

URL 파라미터

// src/pages/Profile.js

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;

- URL 파라미터는 useParams 라는 Hook을 사용하여 객체 형태로 조회 가능

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

 

// src/App.js
import { Route, Routes } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/profiles/:username" element={<Profile />} />
    </Routes>
  );
};

export default App;

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

- 만약 URL 파라미터가 여러개인 경우엔 /profiles/:username/:field 와 같은 형태로 설정

 

쿼리스트링

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

// src/pages/About.js
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;

- 위 컴포넌트에서 useLocation 이라는 Hook을 사용 - 이 Hook은 location 객체를 반환하며 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있음

- 이 객체에는 다음과 같은 값들이 있음

  • pathname: 현재 주소의 경로 (쿼리스트링 제외)
  • search: 맨 앞의 ? 문자 포함한 쿼리스트링 값
  • hash: 주소의 # 문자열 뒤의 값 (주로 History API 가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우터에서 사용합니다.)
  • state: 페이지로 이동할때 임의로 넣을 수 있는 상태 값
  • key: location 객체의 고유 값, 초기에는 default 이며 페이지가 변경될때마다 고유의 값이 생성됨

- 쿼리스트링은 location.search 값을 통해 조회 가능

- 주소창에 http://localhost:3000/about?detail=true&mode=1 라고 직접 입력해서 값 확인 가능

 

- 리액트 라우터에서는 v6부터 useSearchParams 라는 Hook을 통해서 쿼리스트링을 더욱 쉽게 다룰 수 있움

// src/pages/About.js
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;

- useSearchParams 는 배열 타입의 값을 반환하며, 첫번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환

- get 메서드를 통해 특정 쿼리파라미터를 조회할 수 있고, set 메서드를 통해 특정 쿼리파라미터를 업데이트 할 수 있음

- 만약 조회시에 쿼리파라미터가 존재하지 않는다면 null 로 조회

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

 

- 쿼리파라미터를 사용할 때 주의할 점은 쿼리파라미터를 조회할 때 값은 무조건 문자열 타입이라는 것

  • true 또는 false 값을 넣게 된다면 값을 비교할 때 꼭 'true' 와 같이 따옴표로 감싸서 비교
  • 숫자를 다루게 된다면 parseInt 를 사용하여 숫자 타입으로 변환

중첩된 라우트

// src/pages/Articles.js
import { Link } from 'react-router-dom';

const Articles = () => {
  return (
    <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>
  );
};

export default Articles;
// src/App.js: 라우트 설정
import { Route, Routes } from 'react-router-dom';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import Profile from './pages/Profile';

const App = () => {
  return (
    <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>
  );
};

export default App;
// src/pages/Home.js: 게이글 목록 페이지로 가는 링크 추가
import { Link } from 'react-router-dom';

const Home = () => {
  return (
    <div>
      <h1>홈</h1>
      <p>가장 먼저 보여지는 페이지입니다.</p>
      <ul>
        <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>
    </div>
  );
};

export default Home;

- Articles 컴포넌트에서 리액트 라우터에서 제공하는 Outlet 이라는 컴포넌트를 사용

-  이 컴포넌트는 Route  children 으로 들어가는 JSX 엘리먼트를 보여주는 역할

<Route path=":id" element={<Article />} />
// src/pages/Articles.js
import { Link, Outlet } from 'react-router-dom';

const Articles = () => {
  return (
    <div>
      <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;

 

공통 레이아웃 컴포넌트

- 중첩된 라우트와 Outlet 은 페이지끼리 공통적으로 보여줘야 하는 레이아웃이 있을때도 유용하게 사용할 수 있음

ex) Home, About, Profile 페이지에서 상단에 헤더를 보여줘야 하는 상황을 가정

첫 번째로 드는 생각은 아마 Header 컴포넌트를 따로 만들어두고 각 페이지 컴포넌트에서 재사용을 하는 방법

중첩된 라우트를 사용하는 방식을 사용하면 컴포넌트를 한번만 사용해도 된다는 장점 - 상황에 따라 그리고 취향에 따라 구현

 

- 공통 레이아웃을 위한 Layout 컴포넌트

// src/Layout.js
import { Outlet } from 'react-router-dom';

const Layout = () => {
  return (
    <div>
      <header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
        Header
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
};

export default Layout;

- 각 페이지 컴포넌트가 보여져야 하는 부분에 Outlet 컴포넌트를 사용

// src/App.js
import { Route, Routes } from 'react-router-dom';
import Layout from './Layout';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/:username" element={<Profile />} />
      </Route>
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
    </Routes>
  );
};

export default App;

 

index props

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

// src/App.js
import { Route, Routes } from 'react-router-dom';
import Layout from './Layout';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/:username" element={<Profile />} />
      </Route>
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
    </Routes>
  );
};

export default App;

- index prop은 상위 라우트의 경로와 일치하지만, 그 이후에 경로가 주어지지 않았을때 보여지는 라우트를 설정할때 사용

- path="/"와 동일한 역할을 하며 이를 좀 더 명시적으로 표현하는 방법

 

리액트 라우터 부가기능

- 리액트 라우터에는 웹 애플리케이션에서 라우팅에 관련된 작업을 할 때 사용할 수 있는 유용한 API들을 제공

 

useNavigate

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

src/Layout.js
import { Outlet, useNavigate } from 'react-router-dom';

const Layout = () => {
  const navigate = useNavigate();

  const goBack = () => {
    // 이전 페이지로 이동
    navigate(-1);
  };

  const goArticles = () => {
    // articles 경로로 이동
    navigate('/articles');
  };

  return (
    <div>
      <header style={{ background: 'lightgray', padding: 16, fontSize: 24 }}>
        <button onClick={goBack}>뒤로가기</button>
        <button onClick={goArticles}>게시글 목록</button>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
};

export default Layout;

- navigate 함수를 사용할 때 파라미터가 숫자 타입이라면 앞으로 가거나, 뒤로 감

ex) navigate(-1) 을 하면 한 번 뒤로, navigate(-2) 를 하면 두 번 뒤로, navigate(1) 을 하면 앞으로 한 번

 

- 다른 페이지로 이동을 할 때 replace 라는 옵션 - 이 옵션을 사용하면 페이지를 이동할 때 현재 페이지를 페이지 기록에 남김 X

// src/Layout.js - goArticles
const goArticles = () => {
  navigate('/articles', { replace: true });
}

- 만약 { replace: true } 설정이 없었더라면 직전에 봤던 페이지인 About 페이지가 나타나야 하지만, 이 옵션이 활성화되어있기 때문에, 그 전의 페이지인 Home 페이지가 나타남

 

NavLink

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

- 이 컴포넌트를 사용할 때 style 또는 className을 설정할 때 { isActive: boolean } 을 파라미터로 전달받는 함수 타입의 값을 전달

<NavLink 
  style={({isActive}) => isActive ? activeStyle : undefined} 
/>
<NavLink 
  className={({isActive}) => isActive ? 'active' : undefined} 
/>
// src/pages/Articles.js
import { NavLink, Outlet } from 'react-router-dom';

const Articles = () => {
  return (
    <div>
      <Outlet />
      <ul>
        <ArticleItem id={1} />
        <ArticleItem id={2} />
        <ArticleItem id={3} />
      </ul>
    </div>
  );
};

const ArticleItem = ({ id }) => {
  const activeStyle = {
    color: 'green',
    fontSize: 21,
  };
  return (
    <li>
      <NavLink
        to={`/articles/${id}`}
        style={({ isActive }) => (isActive ? activeStyle : undefined)}
      >
        게시글 {id}
      </NavLink>
    </li>
  );
};

export default Articles;

-  현재 NavLink 를 감싼 또 다른 컴포넌트를 만들어서 다음과 같이 리팩토링하여 사용하시는 것을 권장

 

NotFound 페이지 만들기

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

// src/pages/NotFound.js
const NotFound = () => {
  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: 64,
        position: 'absolute',
        width: '100%',
        height: '100%',
      }}
    >
      404
    </div>
  );
};

export default NotFound;
// src/App.js
import { Route, Routes } from 'react-router-dom';
import Layout from './Layout';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import NotFound from './pages/NotFound';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/:username" element={<Profile />} />
      </Route>
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};

export default App;

-  * 는 wildcard 문자: 아무 텍스트나 매칭한다는 뜻

- 이 라우트 엘리먼트의 상단에 위치하는 라우트들의 규칙을 모두 확인하고, 일치하는 라우트가 없다면 이 라우트가 화면에 나타남

 

Navigate 컴포넌트

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

사용자의 로그인이 필요한 페이지인데 로그인을 안했다면 로그인 페이지를 보여주어야 함

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

export default Login;
// src/pages/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;

- 여기서 isLoggedIn은 현재 false라는 고정값을 가지고 있지만, 이 값이 로그인 상태에 따라 true 또는 false를 가르킨다고 가정

- 위 컴포넌트에서는 만약 이 값이 false 라면 Navigate 컴포넌트를 통해 /login 경로로 이동

- 여기서 replace props는 useNavigate 에서 설명한 것과 동일

- 페이지를 이동할 때 현재 페이지를 기록에 남기지 않기 때문에 이동 후 뒤로가기를 눌렀을 때 2 페이지 전의 페이지로 이동

// src/App.js
import { Route, Routes } from 'react-router-dom';
import Layout from './Layout';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import Login from './pages/Login';
import MyPage from './pages/MyPage';
import NotFound from './pages/NotFound';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/profiles/:username" element={<Profile />} />
      </Route>
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
      <Route path="/login" element={<Login />} />
      <Route path="/mypage" element={<MyPage />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};

export default App;
반응형