본문 바로가기

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

[2024 여름방학 React.js 스터디] 정채은 #6주차

반응형

React Router v6 튜토리얼

1. 라우팅이란?

사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것

 

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

 

리액트 라우터: 이 라이브러리는 컴포넌트 기반으로 라우팅 시스템을 설정가능

 

Next.js: 리액트 프로젝트의 프레임워크

 이 프레임워크는 우리가 사용했던 Create React App처럼 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능 제공, 프레임워크의 라우팅 시스템은 파일 경로 기반으로 작동

 

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

한 개의 페이지로 이루어진 애플리케이션

 

이때 드는 의문점 : 리액트 라우터를 사용하여 여러 페이지로 구성된 프로젝트를 만들 수 있다고 했었는데 왜 싱글 페이지 애플리케이션이라고 불리냐?

 

이를 위해 멀티 페이지 애플리케이션은 어떻게 작동하는지 알아보자.

사용자가 다른 페이지로 이동할 때마다 새로운 html을 받아오고, 페이지를 로딩할 때마다 서버에서 CSS, JS, 이미지 파일 등의 리소스를 전달받아 브라우저 화면에 보여줌.

사용자 인터랙션이 별로 없는 정적인 페이지들은 기존의 방식이 적합하지만, 사용자 인터랙션이 많고 다양한 정보를 제공하는 모던 웹 애플리케이션은 부적합.

로운 페이지를 보여주어야 할 때마다 서버 측에서 모든 준비를 한다면 그만큼 서버의 자원을 사용하는 것이고, 트래픽도 더 많이 나올 수 있기 때문

 

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

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

 

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

 

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

 

3-1. 프로젝트 생성 및 라이브러리 설치

리액트 프로젝트를 새로 생성

$ yarn create react-app router-tutorial

 

해당 프로젝트 디렉터리로 이동하여 리액트 라우터 라이브러리를 설치

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

$ cd router-tutorial
$ yarn add react-router-dom

 

3-2. 프로젝트에 라우터 적용

 src/index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사용하여 감싸

 

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

3-3. 페이지 컴포넌트 만들기

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

src 디렉터리에 pages 경로를 만들고, 그 안에 다음 파일들을 생성

 

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;

 

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

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

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

 

그리고, Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 함.

App 컴포넌트를 다음과 같이 Route 컴포넌트를 사용하여 라우트 설정.

 

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;

 

웹 페이지에서는 원래 링크를 보여줄 때 a 태그를 사용, 리액트 라우터를 사용하는 프로젝트에서 a 태그를 바로 사용하면 안됨.

이유 : a 태그를 클릭하여 페이지를 이동할 때 브라우저에서는 페이지를 새로 불러오게 되기 때문

 

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;

 

4. URL 파라미터와 쿼리스트링

이를 사용하기 위해 새로운 페이지 컴포넌트를 만들기.  Profile 컴포넌트를 pages 경로에 다음과 같이 작성.

 

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 와 같은 형태로 설정가능

 

이제, Profile 페이지로 이동을 할 수 있도록 Home 페이지에 Link 를 더 만들기.

링크가 여러 개 이기 때문에, ul 태그를 사용하여 리스트 형태로 보여줌.

 

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>
      </ul>
    </div>
  );
};

export default Home;

 

4-2. 쿼리스트링

쿼리스트링을 사용할 때는 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 객체를 반환하며 이 객체는 현재 사용자가 보고있는 페이지의 정보를 지니고 있음.

 

location 객체의 값들

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

 

쿼리스트링 값이 현재 ?detail=true&mode=1 으로 표시됨.

이 문자열에서 앞에 있는 ? 로 지우고, & 문자열로 분리한뒤 key 와 value 를 파싱하는 작업, 이 작업은 보통 npm 에서 qs 또는 querystring 패키지를 설치해서 처리

 

쿼리스트링을 따로 파싱까지 해야된다면 번거로움.

리액트 라우터에서는 v6부터 useSearchParams 라는 Hook을 통해서 쉽게 ㄱㄴ

 

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

 

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

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

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

 

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

 

5. 중첩된 라우트

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;

 

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

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

 

5.1. 공통 레이아웃 컴포넌트

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

이번에는 중첩된 라우트를 통해 공통 레이아웃 컴포넌트를 사용

우선, 공통 레이아웃을 위한 Layout 컴포넌트를 src 디렉터리에 만들기

 

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 컴포넌트를 사용

 

5-2. index props

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

 

Home 컴포넌트가 사용된 Route 컴포넌트를 다음과 같이 변경

 

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="/"와 동일한 역할을 하며 이를 좀 더 명시적으로 표현하는 방법

 

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

6-1. useNavigate

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

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

다른 페이지로 이동을 할 때 replace 라는 옵션있음

이 옵션을 사용하면 페이지를 이동할 때 현재 페이지를 페이지 기록에 남기지 않음.

 

방금 작성했던 goArticles 함수를 다음과 같이 수정

 

src/Layout.js - goArticles

const goArticles = () => {
  navigate('/articles', { replace: true });
}

그 다음에 / 경로로 들어가서 Home 페이지를 띄운 뒤에, 소개 링크를 눌러 About 페이지로 이동

상단의 게시글 목록 페이지를 눌러보자.

그 상태에서 브라우저의 뒤로가기 버튼을 눌러 이전 페이지로 이동

 

만약 { replace: true } 설정이 없었더라면 직전에 봤던 페이지인 About 페이지가 나타나야 하지만,

이 옵션이 활성화되어있기 때문에, 그 전의 페이지인 Home 페이지가 나타나게 됨.

 

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

 

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

 

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

 

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

 

반복되는 코드가 여러번 사용되면 현재 NavLink 를 감싼 또 다른 컴포넌트를 만들어서 다음과 같이 리팩토링하여 사용 ㄱ

 

6-3. NotFound 페이지 만들기

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

 

이 컴포넌트를 pages 디렉터리에 만들자

 

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;

 

그 다음에 App 컴포넌트를 다음과 같이 수정

 

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 문자인데, 이는 아무 텍스트나 매칭한다는 뜻

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

 

6-4. Navigate 컴포넌트

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

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

 

먼저, 두 페이지 컴포넌트를 pages 디렉터리에 만들기

 

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 에서 설명한 것과 동일

 

 

4장.  API 연동하기

 

1. API 연동의 기본

 

API 연동을 하기 위해서, 우선 프로젝트를 새로 만들기

$ npx create-react-app api-integrate

 

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

$ cd api-integrate
$ yarn add axios

 

axios를 사용해서 GET, PUT, POST, DELETE 등의 메서드로 API 요청가능

REST API 를 사용 할 때에는 하고 싶은 작업에 따라 다른 메서드로 요청가능

 

메서드 들의 의미

  • GET: 데이터 조회
  • POST: 데이터 등록
  • PUT: 데이터 수정
  • DELETE: 데이터 제거

axios 의 사용법

import axios from 'axios';

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

get 이 위치한 자리에는 메서드 이름을 소문자로 넣기

새로운 데이터를 등록하고 싶다면 axios.post() 를 사용

파라미터에는 API 의 주소 넣기

axios.post() 로 데이터를 등록 할 때에는 두번째 파라미터에 등록하고자 하는 정보넣기 가능

 

useState 와 useEffect 로 데이터 로딩하기

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

 

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

  1. 요청의 결과
  2. 로딩 상태
  3. 에러

 

src 컴포넌트에 Users.js 를 생성하고 다음 코드를 작성

 

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;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;

 

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

로딩 상태가 활성화 됐을 땐 로딩중.. 이라는 문구

users 값이 아직 없을 때에는 null 을 보여주도록 처리

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

 

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

버튼을 눌러서 API를 재요청하는 기능 구현

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

반응형