상세 컨텐츠

본문 제목

[리액터 스타터3] project 3 [감정 일기장] 만들기

23-24/React.js 3

by 롱롱😋 2024. 1. 12. 10:00

본문

728x90

 

프로젝트 준비하기

요구사항 분석하기 → 리액트 앱 만들기 → 폰트 설정하기 → 이미지 준비하기 순으로 진행한다.

1. 요구사항 분석하기

어떤 페이지가 있고 무슨 기능이 있는지 알아보도록 하자.

 

Home 페이지

사용자가 앱에 접속하면 처음으로 만나는 페이지다. 인덱스 페이지라고도 부르며 경로는 ‘/’다.

  • 헤더: 월 단위 일기 조회, 좌우버튼으로 날짜 이동
  • 일기 리스트: 일기 정렬, <새 일기 쓰기> 버튼으로 New 페이지로 이동해 일기 추가

New 페이지

새로운 일기를 추가하는 페이지이며 경로는 ‘/new’이다.

  • 헤더: <뒤로 가기> 버튼
  • 날짜 입력, 감정 이미지 선태그 일기 작성 폼
  • 하단: <취소하기>, <작성 완료> 버튼, <작성 완료>버튼은 Home 페이지로 돌아감

Diary 페이지

작성한 일기를 상세히 조회하는 페이지며 경로는 ‘/diary/id’이다. Home에서 일기 리스트를 조회한 다음, 특정 일기를 클릭하면 Diary 페이지로 이동한다. 이런 페이지를 상세 또는 콘텐츠 페이지라고 한다.

  • 헤더: <뒤로 가기>, <수정하기> 버튼, <수정하기> 버튼은 Edit페이지로 이동함
  • 본문: 선택한 일기의 감정 상태와 내용

Edit 페이지

일기를 수정하는 페이지이며 경로는 ‘/edit/id’이다. Home 또는 Diary페이지에서 <수정하기> 버튼을 클릭하면 Edit 페이지로 이동한다. New 페이지와 유사하며 <삭제하기> 버튼이 추가되었다.

 

 

2. 리액트 앱 만들기

[감정 일기장] 프로젝트를 위한 새 리액트 앱을 만들고 불필요한 파일과 코드를 삭제한다.

  1. 폴더 만들기 → CORNERSTUDY/week13
  2. npx create-react-app .
  3. 불필요한 파일과 코드 삭제하기
    1. src/App.test.js
    2. src/logo.svg
    3. src/reportWebVitals.js
    4. setupTest.js
    5. App.js 수정
      import './App.css'; 
      function App() {
      	return ( <div className="App"> </div> ); 
      }
      export default App;​​
    6. index.js 수정
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import './index.css';
      import App from './App';
      
      
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
          <App />
      );

 4. 실행 npm run start

 

 

3. 폰트 설정하기

 리액트 앱에서 사용자가 원하는 폰트를 지정할 때는 파일을 다운로드해 프로젝트에 포함하거나 웹에서 불러오는 방법이 있다. 이 프로젝트에서는 특정 URL로 폰트를 가져오는 ‘웹 폰트’ 방식을 이용한다.

 

 

구글 Fonts에 접속해 폰트 가져오기

https://fonts.google.com/

해당 주소로 접속해 ‘Nanum Pen Script’와 ‘Yeon Sung’ 폰트를 검색하고 Styles 섹션 → Select Regular 400을 클릭해 폰트를 선택한다. 2개의 폰트가 [Selected families]창에 정상적으로 추가되었다면 웹 폰트 주소를 환인하기 위해 <style> 태그 항목에서 @import~로 시작하는 웹 폰트주소를 복사해 index.css에 붙여 다음과 같이 수정한다.

@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');

body{
  font-family: "Nanum Pen Script";
  margin: 0px;
}

 

 

4. 이미지 준비하기

감정 이미지 다운로드하기

https://github.com/winterlood/one-bite-react/releases/tag/emotion

다음 링크로 접속해 프로젝트 src/img 폴더에 파일명 그대로 저장한다.

 

 

이미지를 불러오는 함수 만들기

이미지 번호에 맞게 적절한 이미지를 반환하는 함수를 만드는게 필요하다. src 폴더에서 util.js 파일을 만들고 다음과 같이 작성한다.

  • utils.js
    import emotion1 from "./img/emotion1.png";
    import emotion2 from "./img/emotion2.png";
    import emotion3 from "./img/emotion3.png";
    import emotion4 from "./img/emotion4.png";
    import emotion5 from "./img/emotion5.png";
    
    export const getEmotionImgById = (emotionid) => {
        const targetEmotionId = String(emotionid); // emotionId가 숫자일 수 있으므로 명시적 형 변환
        switch (targetEmotionId) {
            case "1":
                return emotion1;
            case "2":
                return emotion2;
            case "3":
                return emotion3;
            case "4":
                return emotion4;
            case "5":
                return emotion5;
            default:
                return null;
        }
    }​
  • App.js
    • App 컴포넌트에서 getEmotionImgById 함수를 호출해 모든 감정이미지가 렌더링되는지 확인한다. 공통함수 사용을 통해 코드 중복을 최소화한다.
      import './App.css';
      import { getEmotionImgById } from './util';
      
      function App() {
        return (
          <div className="App">
            <img alt="감정1" src={getEmotionImgById(1)} />
            <img alt="감정2" src={getEmotionImgById(2)} />
            <img alt="감정3" src={getEmotionImgById(3)} />
            <img alt="감정4" src={getEmotionImgById(4)} />
            <img alt="감정5" src={getEmotionImgById(5)} />
          </div>
        );
      }
      
      export default App;​

 

 

 

 

페이지 라우팅

라우팅의 기본 개념들

프로젝트를 진행하기에 앞서 페이지 라우팅이 무엇인지 차근차근 알아보도록 하자.

 

라우팅이란?

라우팅은 경로를 의미하는 Route와 진행을 뜻하는 ing가 합쳐진 단어로 ‘경로를 지정하는 과정’이라는 뜻이다.

 

서울에서 사는 사람이 부산에 사는 사람에게 핸드폰으로 메시지를 보낼 때 이 메시지 데이터가 핸드폰에서 다른 핸드폰으로 바로 전송되는 것이 아니라 중간중간 설치된 "라우터"라 불리는 장비를 거친다. 서울에서 부산까지 한 번에 데이터를 전송하기에 거리가 너무 멀기 때문이다.

 

이처럼 라우팅은 “데이터 전달을 목적으로 최적의 경로를 찾아 데이터를 전송하는 과정”이라고 정의할 수 있다.

 

 

페이지 라우팅이란?

페이지 라우팅은 요청에 따라 적절한 페이지를 반환하는 일련의 과정이다.

 

 도메인 주소가 https://winterlood.com인 웹서비스에서 winterlood/blog 또는 winterlood.com/books와 같은 url로 페이지 요청을 하면, 웹서비스는 요청한 blog, books 페이지를 사용자에게 보낸다. 이때 요청 url에서 도메인 주소 뒤에 붙는 blog, books를 ‘경로(path)’라고 한다.

 결국 페이지 라우팅은 “URL 요청 경로에 맞게 적절한 페이지를 보여주는 과정”이다.

 

 

 

 

 

 

리액트의 페이지 라우팅

페이지 라우팅의 구현은 웹 페이지를 어디서 만드느냐에 따라 서버 사이드(Server Side) 렌더링클라이언트 사이드(Client Side) 렌더링 두 가지로 구분한다. 각각 어떻게 동작하는지 알아보도록 하자.

 

 

1) 서버 사이드 렌더링

페이지 라우팅은 다음과 같이 동작한다.

  1. 웹 브라우저에서 winterlood.com/blog라는 url 요청
  2. 웹 서버는 요청 url에서 경로 blog를 확인하고, blog.html을 생성해 반환
  3. 웹 브라우저는 웹 서버에서 반환된 blog.html을 보여줌

사용자가 버튼 또는 링크를 클릭해 페이지를 이동할 때는 다음과 같이 동작한다.

  1. 웹 브라우저에서 winterlood.com/books로 서비스 요청
  2. 웹 서버는 요청 url에서 경로 books를 확인하고 books.html을 생성해 반환
  3. 웹 브라우저는 웹 서버가 반환한 books.html을 보여줌, 이때 페이지가 교체되기 때문에 브라우저 새로고침이 발생함

 

서버 사이드 렌더링은 이처럼 웹 브라우저에 표시할 페이지를 웹 서버에 만들어 전달한다.

  • 장점: 검색 엔진을 최적화하며, 처음 접속할 때 속도가 빠르다.
  • 단점
    • (1) 사용자가 페이지를 이동할 때마다 서버가 새로운 페이지를 생성해 제공하려면 많은 연산을 수행하게 되므로 수많은 요청이 이루어지는 서비스라면 서버에 부하가 걸릴 위험성이 높다.
    • (2) 페이지를 이동할 때마다 브라우저는 서버가 제공하는 페이지를 기다려야 하기 때문에 속도가 느리다.

 

 

2) 클라이언트 사이드 렌더링

리액트 앱은 html 파일이 하나뿐인 단일 페이지 애플리케이션이므로 클라이언트 사이드 렌더링으로 페이지를 라우팅한다.

이는 페이지를 브라우저가 직접 만드는데 다음과 같이 동작한다.

  1. 웹 브라우저가 winterlood.com/blog로 서비스 요청
  2. 웹 서버는 요청 url의 경로를 따지지 않고 페이지의 틀 역할을 하는 index, html과 자바스크립트 애플리케이션인 리액트 앱을 함께 반환
  3. 웹 브라우저는 서버에서 제공된 index.html 페이지를 보여주고 → 자바스크립트로 이루어진 리액트 앱 실행 →리액트 앱은 현재 경로에 맞는 페이지를 보여줌
  4. 사용자가 페이지를 이동하면 웹 브라우저는 서버에서 받은 리액트 앱을 실행해 자체적으로 페이지를 교체

 

클라이언트 사이드 렌더링의 핵심은 사용자가 보는 페이지를 웹서버가 아닌 브라우저가 완성한다는 점이다. 브라우저는 처음 접속할 때만 서버에게 데이터를 요청하며, 페이지를 이동할 때는 별도의 요청을 하지 않는다.

  • 장점: 페이지를 이동할 때 브라우저에서 페이지를 직접 교체하므로 속도가 빠르다.
  • 단점: 서버가 html 파일과 자바스크립트 애플리케이션을 함께 제공하기 때문에, 처음 사이트 접속시 서버 사이드 렌더링보다 속도가 느리다.

 

 

이미지 비교

<좌> 서버 사이드 렌더링       <우> 클라이언트 사이드 렌더링

 

 

 

리액트 라우터로 페이지 라우팅하기

리액트 라우터란?

https://reactrouter.com/

리액트 라우터(React Router)는 Remix 팀에서 제작한 오픈소스 라이브러리이다. 이 페이지 라우팅 전용 라이브러리를 이용하여 필요한 기능을 손쉽게 구현할 수 있다.

 

 

리액트 라우터 설치하기

npm i react-router-dom

 

☑️ 참고

react-router를 핵심 코드로 하여 웹용(react-router-dom)앱용(react-router-native)을 함께 릴리스한다.

 

  • package.json                                                                                                                                                                      해당 파일에서 버전을 확인하고 5이하의 버전이라면 이전 버전을 삭제하고 최신 버전을 설치한다.
    • npm uninstall react-router-dom
    • npm install react-router-dom@6
      "dependencies": {
      
          "react-router-dom": "^6.21.1",
      
        },​

 

 

프로젝트에 라우터 적용하기

  • index.js                                                                                                                                                                              리액트 라우터가 제공하는 BrowserRouter 컴포넌트로 App을 감싼다. BrowserRouter에는 브라우저의 주소 변경을 감지하는 기능이 있다.
    import { BrowserRouter } from 'react-router-dom';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <BrowserRouter>
    	<App />
      </BrowserRouter>
    );​

 

 

페이지 컴포넌트 만들기

페이지 역할을 담당할 컴포넌트는 별도의 폴더(src/pages)로 분리하여 차례대로 작성한다.

  • Home.js: 인덱스 페이지
  • New.js: 새 일기 작성 페이지
  • Diary.js: 일기 상세 조회 페이지
  • Edit.js: 일기 수정, 삭제 페이지
const Home = () => {
    return (
        <div>Home 페이지</div>
    );
};

export default Home;

 

나머지 컴포넌도 이와 같은 형식으로 작성한다.

 

 

 

 

페이지 라우팅 구현하기

URL 경로에 따라 브라우저에 적절한 페이지를 렌더링하도록 페이지 라우팅을 구현한다. react-router-dom이 제공하는 RoutesRoute 컴포넌트를 이용한다.

  • App.js
    • Route가 여러 Route 컴포넌트를 감싸며 현재 URL에 맞게 적절한 Route 컴포넌트를 페이지에 렌더링한다.
      import { Routes, Route } from "react-router-dom";
      import "./App.css";
      import Home from "./pages/Home";
      import New from "./pages/New";
      import Diary from "./pages/Diary";
      import Edit from "./pages/Edit";
      
      
      function App() {
        return (
          <div className="App">
            <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/new" element={<New />} />
              <Route path="/diary" element={<Diary />} />
              <Route path="/edit" element={<Edit />} />
            </Routes>
          </div>
        );
      }
      
      export default App;​

 

< Routes >

자식인 Route 컴포넌트에서 설정한 경로 요청 URL 비교한다. 그리고 정확히 일치하는 컴포넌트를 element 속성에 전달해 렌더링한다. 따라서 설정하지 않은 경로로 접근하면 Routes는 아무것도 페이지에 렌더링하지 않으며, 콘솔에 잘못된 경로로 접근했다고 경고 메시지를 출력한다.

 

 

 

 

페이지 이동 구현하기

경로로 분리된 페이지 간의 이동 방법을 알아보자. 

 

HTML에서는 <a> 태그를 이용해 페이지를 이동했다면, 리액트 라우터로 페이지 라우팅을 구현하는 앱에서는 Link 컴포넌트를 이용한다. to 속성에 경로를 지정해 페이지를 이동한다.

 

💡 <Link to=’이동할 경로’>링크 이름</Link> 

 

클라이언트 사이드 렌더링은 페이지를 서버에 요청하지 않고 브라우저가 직접 이동시키므로 Link 컴포넌트를 사용한다.

브라우저에서 컴포넌트만 교체하는 식으로 렌더링하므로 이동 속도가 매우 빠르다.

 

  • App.js                                                                                                                                                                             모든 페이지로 이동할 수 있는 링크를 만든다.
    import { Routes, Route, Link } from "react-router-dom";
    import "./App.css";
    import Home from "./pages/Home";
    import New from "./pages/New";
    import Diary from "./pages/Diary";
    import Edit from "./pages/Edit";
    
    
    function App() {
      return (
        <div className="App">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/new" element={<New />} />
            <Route path="/diary" element={<Diary />} />
            <Route path="/edit" element={<Edit />} />
          </Routes>
          <div>
            <Link to={"/"}>Home</Link>
            <Link to={"/new"}>New</Link>
            <Link to={"/diary"}>Diary</Link>
            <Link to={"/edit"}>Edit</Link>
          </div>
        </div>
      );
    }​

 

 

 

 

 

리액트 라우터로 동적 경로 라우팅하기

동적경로란 특정 아이템을 나타내는 id처럼 값이 변하는 요소를 URL에 포함하는 경우를 말한다.

 

 

동적 경로의 종류

1. URL 파라미터

   URL에 유동적인 값을 넣는 방법으로, 보통 유동적인 값은 중괄호로 표기한다.

주로 id나이름을 이용해 특정 데이터를 조회할 때 사용한다.

https://localhost:3000/diary/{id}

 

 

2. 쿼리 스트링

   물음표(?) 뒤에 key=value 문법으로 URL에 유동적인 값을 포함하는 방법이다. 만약 유동적인 값을 두 개 이상 포함해야 한다면 &로 구분한다.

보통 게시물 리스트에서 사용자가 정렬 조건을 선택하거나 현재 조회하는 게시판의 페이지를 표현할 때 사용한다.

https://localhost:3000?sort=latest

 

 

 

동적 경로에 대응하기

URL 파라미터로 경로 설정하기

Diary페이지는 특정 id를 가진 일기를 상세 조회할 때 사용한다. 동적 경로가 포함된 페이지를 라우팅하려면, Route 컴포넌트에서 URL 파라미터 방식으로 전달해야 한다.

  • App.js
    <Route path="/diary/:id" element={<Diary />} />​

 

 

URL 파라미터 값 불러오기

앞서 URL 파라미터로 전달한 일기 id를 불러와 Diary 페이지에서 사용하도록 작성한다. 이때 react-router-dom이 제공하는 리액트 훅 useParams를 이용한다.

  • Diary.js
    import { useParams } from "react-router-dom";
    
    const Diary = () => {
        const { id } = useParams();
    
        return (
            <div>
                <div>{id}번 일기</div>
                <div>Diary 페이지</div>
            </div>
        );
    };
    
    export default Diary;

 

useParams은 브라우저에서 URL을 입력하면 이 경로에 포함된 URL 파라미터를 객체 형태로 반환한다. 이 객체의 파라미터 값을 사용하려면 객체를 구조 분해 할당해 필요한 값만 꺼내 쓰면 된다.

 

 

 

 

쿼리 스트링으로 값 불러오기

 쿼리 스트링URL 경로 다음에 ?로 구분하므로 URL 파라미터처럼 페이지 라우팅을 위한 별도의 경로 설정이 필요 없다.

react-router-domuseSearchParams 훅을 이용하여 URL에 있는 쿼리 스트링 값을 꺼내 사용할 수 있다.

  • Home.js
    • useSearchParams가 반환한 첫 번째 요소에서 sort 값을 불러와 콘솔에 출력한다.
      import { useSearchParams } from "react-router-dom";
      
      const Home = () => {
          const [searchParams, setSearchParams] = useSearchParams();
          console.log(searchParams.get("sort"));
      
          return (
              <div>Home 페이지</div>
          );
      };
      
      export default Home;

쿼리 스트링으로 설정한 sort값을 잘 불러오는지 확인

 

 

 

useSearchParams은 useState처럼 배열 형태로 값을 반환한다.

  • 첫 번째 요소: 조회, 수정이 가능한 메서드를 포함하고 있는 쿼리 스트링 객체
  • 두 번째 요소: 이 객체를 업데이트하는 함수

 

 

 

 

 

Quiz 

1. 특정 URL로 폰트를 가져오는 방식을 (웹 폰트) 방식이라고 한다.

2. winterlood/blog 또는 winterlood.com/books와 같은 url에서  도메인 주소 뒤에 붙는 blog, books를 (경로)라고 한다.

3. URL 요청 경로에 맞게 적절한 페이지를 보여주는 과정을 (페이지 라우팅)이라고 한다.

4. 리액트 페이지 라우팅은 (웹 페이지)를 어디서 만드느냐에 따라 2가지로 나뉜다.

5. (클라이언트 사이드 렌더링)의 핵심은 사용자가 보는 페이지를 웹서버가 아닌 브라우저가 완성한다는 점이다.

6. (동적 경로)란 특정 아이템을 나타내는 id처럼 값이 변하는 요소를 URL에 포함하는 경우를 말한다.

7. ( useSearchParams) )훅은 useState처럼 배열 형태로 값을 반환한다. 

 

 

프로그래밍 문제

1. HTML에서는 <a>태그를 이용해 페이지를 이동한다. 그렇다면 다음 코드에서 리액트 라우터로 페이지 라우팅을 구현하는 앱에서 페이지를 이동하는 코드를 완성한다.

import { Routes, Route } from "react-router-dom";
import "./App.css";
import Home from "./pages/Home";
import New from "./pages/New";
import Diary from "./pages/Diary";
import Edit from "./pages/Edit";


function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
      <div>
	//이 부분 작성
      </div>
    </div>
  );
}

 

 

 

2. 쿼리스트링으로 설정한 sort값을 불러와 console창에 입력하는 코드를 작성한다. 

(힌트: import문 괄호안에 들어갈 훅을 사용하기)

import {  } from "react-router-dom";

const Home = () => {
    // 이 부분 작성

    return (
        <div>Home 페이지</div>
    );
};

export default Home;

 

 

 

 


1.

import { Routes, Route, Link } from "react-router-dom";

 

<Link to={"/"}>Home</Link>

 

 

2. 

import { useSearchParams } from "react-router-dom";

const Home = () => {
    const [searchParams, setSearchParams] = useSearchParams();
    console.log(searchParams.get("sort"));

    return (
        <div>Home 페이지</div>
    );
};

export default Home;

 

 

 

 

 

 

 

출처 : 이정환, 『한 입 크기로 잘라 먹는 리액트』, 인사이트, p409-438

Corner React.js

Editor: so2

728x90

관련글 더보기