상세 컨텐츠

본문 제목

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

23-24/React.js 2

by YUZ 유즈 2024. 1. 12. 10:00

본문

728x90

프로젝트 준비하기

[감정 일기장] 앱을 만들기 위한 순서는 다음과 같다.

  1. 요구사항 분석하기
  2. 리액트 앱 만들기
  3. 폰트 설정하기
  4. 이미지 준비하기

 

요구사항 분석하기

[감정 일기장]은 일기를 작성하면서 그날의 자기감정을 표현하는 서비스이다.

총 4페이지로 구성되어 있다.

 

Home 페이지

  • 인덱스 페이지: 사용자가 앱에 접속하면 처음으로 만나는 페이지이며, 경로는 '/'이다.
  • 상단 헤더 섹션과 일기 리스트로 구성되어 있다.

상단 헤더 섹션: 월 단위로 일기를 조회하는 기능, 좌우 버튼을 클릭하면 월 단위로 날짜를 이동한다.

일기 리스트 섹션: '최신순', '오래된 순'으로 일기 리스트를 정렬하는 기능, 새로운 일기를 추가하는 기능이 있다.

<새 일기 쓰기> 버튼을 누르면 새로운 일기를 작성하는 New 페이지로 이동한다.

New 페이지

  • 새로운 일기를 추가하는 페이지이며 경로는 '/new'이다.
  • <뒤로 가기> 버튼이 있는 헤더
  • 날짜 입력
  • 감정 이미지 선택
  • 일기 작성 폼

사용자가 New 페이지에서 일기를 작성하고 <작성 완료> 버튼을 누르면, Home으로 돌아간다.

이때 작성한 일기가 Home 페이지의 리스트에 추가된다.

 

Diary 페이지

  • 작성한 일기를 상세히 조회하는 페이지이며 경로는 '/diary/일기 id'이다.
  • <뒤로 가기> 버튼
  • <수정하기> 버튼

Home에서 일기 리스트를 조회한 다음, 특정 일기를 클릭하면 Diary 페이지로 이동한다.

이런 페이지를 상세 / 콘텐츠 페이지라고 한다.

 

Edit 페이지

  • 작성한 일기를 수정하는 페이지이며 경로는 'edit/(일기) id'이다.
  • <삭제하기> 버튼

 

리액트 앱 만들기

  1. 새 폴더 project3을 만든다.
  2. npx create-react-app. 명령을 실행한다.
  3. src 폴더에서 App.js, logo.svg, reprotWebVials.js, setupTest.js를 삭제한다.
  4. 불필요한 코드를 삭제한다.
  5. npm run start 명령을 실행한다.
import "./App.css";
function App() {
  return <div className="App"></div>;
}
export default App;
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 />);

 

 

폰트 설정하기

[감정 일기장] 프로젝트에 적용할 폰트를 설정한다.

  • 파일을 다운로드하여 프로젝트에 포함한다.
  • 웹에서 불러온다.

웹 폰트: 특정 URL로 폰트를 가져오는 방식이다.

 

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

https://fonts.google.com

 

Browse Fonts - Google Fonts

Making the web more beautiful, fast, and open through great typography

fonts.google.com

 

  • 해당 프로젝트에서는 'Nanum Pen Script'와 'Yeon Sung' 폰트를 사용한다.

 

  1. 검색창에 폰트 이름을 검색한다.
  2. 'Styles' 섹션으로 이동하여 'Select Regular 400'을 클릭한다.
  3. 'Selected families' 창에 자동으로 폰트가 추가된다.
  4. 'Selected families' 창에 있는 <style> 태그 항목을 찾는다.
  5. @import url로 시작하는 구문을 찾아서 복사한다.
  6. 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;
}

 

가져온 폰트 적용하기

App.js를 다음과 같이 수정한다.

import "./App.css";
function App() {
  return (
    <div className="App">
      <h1>감정 일기장</h1>
    </div>
  );
}
export default App;

 

두 폰트 모두 잘 불러와지는지 확인해 본다.

(...)

body {
  font-family: "Yeon Sung";
  margin: 0px;
}

 

 

이미지 준비하기

[감정 일기장] 프로젝트에서 사용할 이미지를 다운로드하여 페이지에 렌더링 하는 방법을 알아보자.

 

감정 이미지 다운로드 하기

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

다음 링크에 접속하여 emotion.zip을 다운로드한다.

 

Release 감정 일기장 이미지 · winterlood/one-bite-react

최종 프로젝트 감정 일기장에 사용 되는 5개의 감정 이미지 파일입니다.

github.com

 

src에 img 폴더를 생성하고 파일명 그대로 저장한다.

 

App.js를 다음과 같이 수정한다.

import emotion1 from "./img/emotion1.png";
function App() {
  return (
    <div className="App">
      <img alt="감정1" src={emotion1} />
    </div>
  );
}
export default App;

 

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

src 폴더에서 util.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);
  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 컴포넌트에서 함수 getEmotionImgById를 호출해 모든 감정 이미지를 페이지에 렌더링 한다.

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가 합쳐진 단어로, '경로를 지정하는 과정'이라는 뜻이다.

데이터 전달을 목적으로 최적의 경로를 찾아 데이터를 전송하는 과정이다.

 

페이지 라우팅이란?

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

URL 요청 경로에 맞게 적절한 페이지를 보여주는 과정이다.

 

리액트의 페이지 라우팅

  • 서버 사이드(Server Side) 렌더링
  • 클라이언트 사이드(Client Side) 렌더링

해당 프로젝트에서는 브라우저에서 페이지를 만드는 클라이언트 사이드 렌더링 방식을 사용한다.

 

서버 사이드 렌더링

  1. 웹 브라우저에서 URL로 서비스를 요청한다.
  2. 웹 서버는 요청 URL에서 경로를 확인하고 해당 html을 생성해 반환한다.
  3. 웹 브라우저는 웹 서버에서 반환된 html을 보여준다.

해당 페이지 라우팅을 서버 사이드 렌더링이라고 한다.

해당 방식은 웹 브라우저에 표시할 페이지를 웹 서버에서 만들어 전달한다.

 

클라이언트 사이드 렌더링

  1. 웹 브라우저가 URL로 서비스를 요청한다.
  2. 웹 서버는 요청 URL의 경로를 따지지 않고 페이지의 틀 역할을 하는 index, html과 리액트 앱을 함께 반환한다.
  3. 웹 브라우저는 서버에서 제공된 index.html 페이지를 보여주고 리액트 앱을 실행한다.
  4. 리액트 앱은 현재 경로에 맞는 페이지를 보여준다.
  5. 사용자가 페이지를 이동하면 웹브라우저는 서버에서 받은 리액트 앱을 실행해 자체적으로 페이지를 교체한다.

해당 페이지 라우팅을 클라이언트 사이드 렌더링이라고 한다.

해당 방식은 페이지를 브라우저가 직접 만든다.

 

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

리액트 라우터란?

https://reactrouter.com

 

Home v6.21.1 | React Router

I'm on v5 The migration guide will help you migrate incrementally and keep shipping along the way. Or, do it all in one yolo commit! Either way, we've got you covered to start using the new features right away.

reactrouter.com

  • Remix 팀에서 제작한 오픈 소스 라이브러리이다.
  • 해당 라우터를 이용하면 단 몇 줄의 코드만으로 여러 페이지로 구성된 리액트 앱을 간단히 구축할 수 있다.

 

리액트 라우터 설치하기

npm i react-router-dom

 

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

index.js를 다음과 같이 작성한다.

(...)
import { BrowserRouter } from "react-router-dom";

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

 

BrowserRouter에는 브라우저의 주소 변경을 감지하는 기능이 있다.

 

페이지 컴포넌트 만들기

  • Home: 인덱스 페이지
  • New: 새 일기 작성 페이지
  • Diary: 일기 상세 조회 페이지
  • Edit: 작성한 일기를 수정하거나 삭제하는 페이지

src 아래에 pages 폴더를 생성한다.

 

Home

pages 폴더에 Home.js를 만들고 다음과 같이 작성한다.

const Home = () => {
  return <div>Home 페이지입니다</div>;
};
export default Home;

 

New

pages 폴더에 New.js를 만들고 다음과 같이 작성한다.

const New = () => {
  return <div>New 페이지입니다</div>;
};
export default New;

 

Diary

pages 폴더에 Diary.js를 만들고 다음과 같이 작성한다.

const Diary = () => {
  return <div>Diary 페이지입니다</div>;
};
export default Diary;

 

Edit

pages 폴더에 Edit.js를 만들고 다음과 같이 작성한다.

const Edit = () => {
  return <div>Edit 페이지입니다</div>;
};
export default Edit;

 

페이지 라우팅 구현하기

App.js를 다음과 같이 수정한다.

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 경로와 일치하는 요소를 찾아 페이지에 렌더링 한다.

 

페이지 이동 구현하기

  • 리액트 라우터로 페이지 라우팅을 구현하는 앱에서는 Link라는 컴포넌트를 이용한다.
  • Link 컴포넌트의 사용법: <Link to='이동할 경로'> 링크 이름 </Link>
  • to 속성에 경로를 지정해 페이지를 이동한다.

App.js를 다음과 같이 수정한다.

import { Routes, Route, Link } from "react-router-dom";
(...)
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>
  );
}
export default App;

 

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

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

 

동적 경로 종류

  • URL 파라미터
  • 쿼리 스트링

 

URL 파라미터

  • URL에 유동적인 값을 넣는 방법이다.
  • id나 이름을 이용해 특정 데이터를 조회할 때 사용한다.

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

예시) id가 3인 일기 상세 페이지의 URL

-> https://localhost:3000/diary/3

 

쿼리 스트링

  • 물음표(?) 뒤에 key=value 문법으로 URL에 유동적인 값을 포함하는 방법이다.
  • URL에 유동적인 값을 두 개 이상 포함한다면 &으로 구분한다.
  • 게시물 리스트에서 사용자가 정렬 조건을 선택하거나 현재 조회하는 게시판의 페이지를 표현할 때 사용한다.

https://localhost:3000?sort=latest

https://localhost:3000?sort=latest&page=1

 

동적 경로에 대응하기

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

  •  [감정 일기장] 프로젝트에서 Diary 페이지는 특정 id를 가진 일기를 상세 조회할 때 사용한다.
  • Route 컴포넌트에서 URL 파라미터 방식으로 전달해야 한다.

App 컴포넌트에서 다음 부분을 수정한다.

<Route path="/diary/:id" element={<Diary />} />

 

URL 파라미터 값 불러오기

  • URL 파라미터로 전달한 일기 id를 불러와 Diary 페이지에서 사용한다.

Diary.js를 다음과 같이 수정한다.

import { useParams } from "react-router-dom";
const Diary = () => {
  const params = useParams();
  console.log(params);
  return <div>Diary 페이지입니다</div>;
};
export default Diary;

 

 

  • 객체를 구조 분해 할당해 필요한 값만 꺼내 쓰면 된다.

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;

 

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

  • useSearchParams라는 리액트 훅을 이용하여 URL에 있는 쿼리 스트링 값을 꺼내 사용한다.

Home 컴포넌트를 다음과 같이 수정한다.

import { useSearchParams } from "react-router-dom";
const Home = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  console.log(searchParams.get("sort"));
  return <div>Home 페이지입니다</div>;
};
export default Home;

QUIZ

  1. 앱에 접속하면 처음으로 만나는 페이지를 ( )라고 부르며 경로는 '/'이다.
  2. ( ) 방식은 특정 URL로 폰트를 가져오는 방식이다.
  3. ( )은 데이터 전달을 목적으로 최적의 경로를 찾아 데이터를 전송하는 과정이다.
  4. ( )은 요청에 따라 적절한 페이지를 반환하는 일련의 과정이다.
  5. 웹 브라우저에 표시할 페이지를 웹 서버에서 만들어 전달하는 렌더링 방식은 ( )이다.
  6. 브라우저에서 페이지를 만드는 렌더링 방식은 ( )이다.
  7. ( )는 특정 아이템을 나타내는 id처럼 값이 변하는 요소를 URL에 포함하는 경우이다.

  1.  웹 폰트 방식을 이용하여 구글 Fonts에서 Nanum Pen Script를 불러오는 코드를 작성해라.
  2.  다음 Routes 태그를 보고 Link 컴포넌트를 이용하여 페이지를 이동하도록 링크를 작성해 봐라.
<Routes>
    <Route path="/" element={<Home />} />
    <Route path="/new" element={<New />} />
    <Route path="/diary" element={<Diary />} />
    <Route path="/edit" element={<Edit />} />
</Routes>

정답

  1. 인덱스 페이지
  2. 웹 폰트
  3. 라우팅
  4. 페이지 라우팅
  5. 서버 사이드 렌더링
  6. 클라이언트 사이드 렌더링
  7. 동적 경로

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

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

 

  2. 

<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>

이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).

 

Editor: 숨숨

728x90

관련글 더보기