본문 바로가기
IT/React

[React] 서버리스 메모앱-2

by 밤톨엽 2022. 4. 18.

해당 글은 [React] 서버리스 메모앱-1와 이어집니다.

이번 글에서는 리액트 라우터에, AWS Amplify, 로그인 폼에 대해서 작성을 하였습니다.


React 라우터 설치

Create React App은 기본적으로 많은 것을 설정해주지만 경로를 처리하는 방법은 제공되지 않는다. 현재 구성중인 React 앱은 단일 페이지로 동작하기 때문에 React Router 를 사용하여 이를 처리해보자.

React Router를 사용하면 /login 처럼 경로를 지정할 수 있으며 사용자가 해당 페이지로 이동할 때 로드되어야 하는 React Component를 지정할 수 있다.

터미널에서 frontend/ 경로가 맞는지 확인 후 아래 명령어를 실행하자

npm install react-router-dom@5.2.0 --save
npm install --save-dev @types/react-router-dom

React 라우터 설정

App 컴포넌트는 현재 src/App.tsx 경로에 위치하며 전체 앱의 컨테이너로 사용 중이다. 여기에 Router를 적용시키기 위해 src/index.tsx 파일의 코드를 아래처럼 변경해주자.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter as Router } from 'react-router-dom';

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

변경한 사항에 대해서 살펴보자.

  1. BrowserRouter는 브라우저의 History API를 사용하여 실제 URL을 생성해준다.
  2. Router 컴포넌트를 사용하여 App을 렌더링 하는 데 사용한다. App 컴포넌트는 이를 통해 내부에 필요한 경로를 생성한다.

이제 npm run start 명령어로 브라우저를 실행하면 이전과 똑같이 보여야 한다. 차이점은 React Router를 사용하여 페이지를 렌더링 하는 것뿐이다.

컨테이너 생성

메모를 추가 / 조회 / 수정 / 삭제 를 위해 몇 가지 다른 페이지를 생성해야 한다. 여기서 해당 페이지들을 감싸서 렌더링 하는 최상위 구성 요소인 컨테이너를 생성해보자.
(메모 동작에 관련된 페이지는 컨테이너 컴포넌트를 구성하여 자식 컴포넌트로 렌더링 할 예정)

Navbar 추가

우선 Navbar 영역을 추가하여 상단에 메뉴창을 구성해보겠다. 여기서 Navbar React-Bootstrap 구성 요소를 사용한다.

src/App.tsx 경로의 파일을 아래와 같이 수정하자.

import React from "react";
import Navbar from "react-bootstrap/Navbar";
import "./App.css";

function App() {
  return (
    <div className="App container py-3">
      <Navbar collapseOnSelect bg="light" expand="md" className="mb-3">
        <Navbar.Brand className="font-weight-bold text-muted">
          Scratch
        </Navbar.Brand>
        <Navbar.Toggle />
      </Navbar>
    </div>
  );
}

export default App;

구성한 요소들을 몇 가지 살펴보자.

  1. 부트스트랩에서 지원하는 고정 너비 컨테이너 div.container 생성
  2. fluid 속성을 사용하여 컨테이너의 너비에 맞게 Navbar를 추가
  3. 부트스트랩 간격 유틸리티 클래스 (예: mb-#, py-#)를 사용하여 여백 하단(mb) 및 패딩 수직(py)을 추가
    (UI에 조화로운 느낌을 주기 위해 비례하는 spacer units 단위를 사용한다.)

src/App.css 파일에 있는 내용을 아래처럼 교체해주자.

.App {
}

Home 컨테이너 생성

이제 / 경로로 응답하는 홈페이지 메인용 컨테이너를 추가하자.

src/containers/Home.tsx 경로로 폴더 및 컴포넌트 파일을 생성해서 아래처럼 코드를 구성하자.

import React from "react";
import "./Home.css";

export default function Home() {
  return (
    <div className="Home">
      <div className="lander">
        <h1>Scratch</h1>
        <p className="text-muted">심플한 메모 앱</p>
      </div>
    </div>
  );
}

src/containers/Home.css 경로로 스타일 파일을 생성해서 아래처럼 스타일을 구성하자.

.Home .lander {
  padding: 80px 0;
  text-align: center;
}

.Home .lander h1 {
  font-family: "Open Sans", sans-serif;
  font-weight: 600;
}

경로 설정

리액트 앱이 경로에 응답할 수 있도록 src/Routes.tsx 파일을 생성하고 아래처럼 코드를 구성해주자.

import React from "react";
import { Route, Switch } from "react-router-dom";
import Home from "./containers/Home";

export default function Routes() {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
    </Switch>
  );
}

Switch 컴포넌트를 구성하고 그 안에 Route 컴포넌트를 사용하여 path 속성의 경로와 일치할 경우 렌더링이 가능하도록 구성했다.

Home 컴포넌트는 exact 속성을 사용하여  /  경로와 정확히 일치하는지 확인하는 속성이다.

개발환경에서 인터넷 주소창에 http://localhost:3000 로 이동할 경우 <Home /> 컴포넌트가 렌더링 된다.

경로 렌더링

Home 컴포넌트를 렌더링 하기 위해 경로를 설정해주자.

src/app.tsx 파일에서 상단에 Routes 컴포넌트를 추가해주자.

import Routes from "./Routes"; // 상단에 추가

// 변경해주자
function App() {
  return (
    <div className="App container py-3">
      <Navbar collapseOnSelect bg="light" expand="md" className="mb-3">
        <Navbar.Brand className="font-weight-bold text-muted">
          Scratch
        </Navbar.Brand>
        <Navbar.Toggle />
      </Navbar>
      <Routes />
    </div>
  );
}

위에까지 구성 후 브라우저를 확인하면 아래처럼 보이게 된다.

Home
Home

Navbar에 링크 추가하기

이제 Navbar 영역의 표시줄에 대한 몇 가지 링크를 추가하자. 이는 사용자가 앱을 처음 방문할 때 로그인하거나 우리 메모 웹 애플리케이션에 회원 가입할 수 있는 링크를 제공한다.

src/App.tsx 파일의 function App() 부분의 코드를 아래처럼 수정해주자.

import Nav from "react-bootstrap/Nav"; // --> 상단에 추가

function App() {
  return (
    <div className="App container py-3">
      <Navbar collapseOnSelect bg="light" expand="md" className="mb-3">
        <Navbar.Brand href="/" className="font-weight-bold text-muted">
          Scratch
        </Navbar.Brand>
        <Navbar.Toggle />
        <Navbar.Collapse className="justify-content-end">
          <Nav>
            <Nav.Link href="/signup">회원가입</Nav.Link>
            <Nav.Link href="/login">로그인</Nav.Link>
          </Nav>
        </Navbar.Collapse>
      </Navbar>
      <Routes />
    </div>
  );
}

위에처럼 부트스트랩에서 제공하는 링크를 구성해서 사용하면 브라우저가 새로고침 된다. 애플리케이션을 빌드하기 때문에 페이지를 새로고침 하지 않고 필요한 페이지만 렌더링 되도록 수정해보자.


위 문제를 React Router Bootstrap이라는 React Router 및 React Bootstrap과 함께 작동하는 구성 요소 패키지를 설치하여 해결하자.

터미널에서 아래 명령어를 실행하여 해당 패키지를 설치해주자.

npm install react-router-bootstrap@0.25.0
npm i --save-dev @types/react-router-bootstrap

src/App.tsx 파일의 function App() 부분의 코드를 아래처럼 수정해주자.

import { LinkContainer } from "react-router-bootstrap"; // --> 상단에 추가

function App() {
  return (
    <div className="App container py-3">
      <Navbar collapseOnSelect bg="light" expand="md" className="mb-3">
        <LinkContainer to="/">
          <Navbar.Brand className="font-weight-bold text-muted">
            Scratch
          </Navbar.Brand>
        </LinkContainer>
        <Navbar.Toggle />
        <Navbar.Collapse className="justify-content-end">
          <Nav activeKey={window.location.pathname}>
            <LinkContainer to="/signup">
              <Nav.Link>회원가입</Nav.Link>
            </LinkContainer>
            <LinkContainer to="/login">
              <Nav.Link>로그인</Nav.Link>
            </LinkContainer>
          </Nav>
        </Navbar.Collapse>
      </Navbar>
      <Routes />
    </div>
  );
}

여기서 잠깐 구성한 코드를 살펴보면 window.location 객체에서는 현재 경로에 대한 정보를 가지고 있다.

해당 정보로 activeKey 속성에 밑에 LinkContainer와 일치하면 경로 값을 넣어주면 해당 링크에 활성화된 상태를 표현할 수 있다.

404 페이지 핸들

사용자가 존재하지 않는 URL을 직접 입력하거나 이동하는 경우 사용자에게 올바른 리액션을 주기 위해서 React Router로 404 관련 처리를 구성해보자.

src/containers/NotFound.tsx 경로로 파일을 생성하고 아래처럼 코드를 구성하자.

import React from "react";
import "./NotFound.css";

export default function NotFound() {
  return (
    <div className="NotFound text-center">
      <h3>현재 요청하신 페이지는 존재하지 않습니다.</h3>
    </div>
  );
}

src/containers/NotFound.css 경로로 스타일 파일을 생성하여 아래처럼 스타일을 추가하자.

.NotFound {
    padding-top: 100px;
}

Routes 404 구성

404를 처리하기 위해 NotFound 컴포넌트를 src/Routes.tsx 파일에서 <Switch> 스코프 안에서 마지막 경로로 구성해주자.

import NotFound from "./containers/NotFound"; // --> 상단에 추가

export default function Routes() {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      {/* 가장 마지막에 선언해주자 */}
      <Route>
        <NotFound />
      </Route>
    </Switch>
  );
}

이제 브라우저에서 로그인 또는 회원 가입 링크를 클릭하면 404 메시지가 표시되어야 한다.

404 페이지
404 페이지

AWS Amplify 구성

우리는 사용자가 앱에 로그인하고 가입할 수 있도록 구성해야 한다.  AWS Amplify 라는 라이브러리를 사용하면 우리가 구성한 AWS Serverless Backend에 보다 간편하게 연결할 수 있다.

터미널에서 아래 명령어를 실행하여 AWS Amplify 패키지를 설치하자.

npm install aws-amplify

구성파일 생성

우리가 만든 모든 리소스를 참조할 수 있도록 중요한 값들을 구성 파일에 선언하자.

src/config.ts 파일을 생성하여 아래처럼 구성해보자.

const config = {
  // Backend config
  s3: {
    REGION: process.env.REACT_APP_REGION,
    BUCKET: process.env.REACT_APP_BUCKET,
  },
  apiGateway: {
    REGION: process.env.REACT_APP_REGION,
    URL: process.env.REACT_APP_API_URL,
  },
  cognito: {
    REGION: process.env.REACT_APP_REGION,
    USER_POOL_ID: process.env.REACT_APP_USER_POOL_ID,
    APP_CLIENT_ID: process.env.REACT_APP_USER_POOL_CLIENT_ID,
    IDENTITY_POOL_ID: process.env.REACT_APP_IDENTITY_POOL_ID,
  },
};

export default config;

우리는 @serverless-stack/static-site-env 패키지를 구성해놓았으며 환경변수 값들을 불러올 수 있다.

AWS Amplify 추가

src/index.tsx 파일에서 AWS Amplify와 관련해서 아래처럼 구성해주자.

import { Amplify } from 'aws-amplify'; // 상단에 추가
import config from './config'; // 상단에 추가

// ReactDom.render 부분 위에 추가
Amplify.configure({
  Auth: {
    mandatorySignIn: true,
    region: config.cognito.REGION,
    userPoolId: config.cognito.USER_POOL_ID,
    identityPoolId: config.cognito.IDENTITY_POOL_ID,
    userPoolWebClientId: config.cognito.APP_CLIENT_ID
  },
  Storage: {
    region: config.s3.REGION,
    bucket: config.s3.BUCKET,
    identityPoolId: config.cognito.IDENTITY_POOL_ID
  },
  API: {
    endpoints: [
      {
        name: "notes",
        endpoint: config.apiGateway.URL,
        region: config.apiGateway.REGION
      },
    ]
  }
});

Amplify.configure() 설정은 다양한 AWS 리소스에 접근하기 위해 설정하는 것이다. 여기서는 구성 외에 다른 특별한 작업을 수행하지 않는다.

로그인 페이지

사용자가 자격 증명으로 로그인할 수 있는 페이지를 만들어 보자. 우리는 AWS Cognito에서 사용자가 이메일로 로그인하고 가입할 수 있도록 설정을 진행했었다.

src/containers/Login.tsx 파일을 추가하고 아래처럼 UI 코드를 구성해주자.

import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import "./Login.css";

export default function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  function validateForm() {
    return email.length > 0 && password.length > 0;
  }

  function handleSubmit(event: any) {
    event.preventDefault();
  }

  return (
    <div className="Login">
      <Form onSubmit={handleSubmit}>
        <Form.Group controlId="email">
          <Form.Label>이메일</Form.Label>
          <Form.Control
            autoFocus
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </Form.Group>
        <Form.Group controlId="password">
          <Form.Label>비밀번호</Form.Label>
          <Form.Control
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </Form.Group>
        <Button block size="lg" type="submit" disabled={!validateForm()}>
          로그인
        </Button>
      </Form>
    </div>
  );
}

우리는 위에서 몇 가지 React 문법과 함께 로그인 처리를 구현하고 있다.
(좀 더 자세한 이해를 위해 아래 리스트에서 키워드를 클릭하여 React 문서를 살펴보자.)

  1. useState Hook 을 사용해서 사용자가 Form에 입력한 데이터를 저장한다. useState 는 값을 저장하는 공간과 현재 값과 새 값을 설정할 수 있는 함수를 제공한다.
    (React에서 Dom이 렌더링 되는 시점들이 있으니 Lifecycle method Diagram 링크를 한번 살펴보는 것을 추천)
  2. setEmail(), setPassword() 함수를 사용하여 두개의 입력창에 상태를 연결하였다. 또한, 사용자가 입력한 내용을 각 함수에 e.target.value 인자로 전달하여 값을 설정하게 된다.
    (e.target.value 값이 변경되어 새 값이 설정되면 연결된 <Form.Control /> 요소가 다시 렌더링 된다.)
  3. <Form /> 에서 email, password 값을 표시하도록 설정하고다. React에서 <Form /> 형태로 값을 상태 변수로 표시하고 사용자가 무언가를 입력할 때 새 값을 설정하는 패턴을 Controlled Component 라고 한다.
  4. autoFocus 속성은 페이지가 로드될 때 이 필드에 포커스가 되도록 email 필드에 플래그를 설정하고 있다.
  5. <Button />validateForm() 함수를 연결하여 특정 조건을 만족할 때 버튼을 활성화하도록 한다.
  6. 마지막으로 Submit 할 경우 handleSubmit() 함수를 호출한다.

src/containers/Login.css 파일을 추가하고 스타일을 구성해주자.

@media all and (min-width: 480px) {
  .Login {
    padding: 60px 0;
   }

  .Login form {
    margin: 0 auto;
    max-width: 320px;
  }
}

경로 추가

src/Routes.tsx 파일에서 <Home /> 경로 다음에 아래 코드를 추가하자.

import Login from "./containers/Login"; // 상단에 추가

// <Home /> 경로 구성 바로 아래 추가
<Route exact path="/login">
  <Login />
</Route>

경로 추가 완료 후 브라우저에서 로그인 버튼을 클릭하면 아래처럼 표시가 되어야 한다.

Login
Login

이제 마지막으로 Github에 작성한 소스를 올려주자

[React] 서버리스 메모앱-2

  • React Router 구성
  • AWS Amplify 설정
  • 로그인 페이지 구성

이번 문서에서는 위와 같이 React Router를 구성하고 Backend에 연결하기 위해 AWS Amplify를 설정했으며 로그인 페이지의 기초를 구성하였습니다.

다음 파트부터는 AWS Cognito 로그인, 세션, 로그인&로그아웃에 대해 다루게 되며 [React] 서버리스 메모앱-3 파트에서 정리하도록 하겠습니다.

'IT > React' 카테고리의 다른 글

[React] 서버리스 메모앱-5  (0) 2022.05.10
[React] 서버리스 메모앱-4  (0) 2022.05.07
[React] 서버리스 메모앱-3  (0) 2022.04.24
[React] 서버리스 메모앱-1  (1) 2022.04.17