감정 일기장 앱을 만들기 위한 프로젝트 준비는 다음 순서와 같다.
1. 요구사항 분석하기
2. 리액트 앱 만들기
3. 폰트 설정하기
4. 이미지 준비하기
1-(1) 요구사항 분석하기
이 프로젝트의 최종 목표인 '감정 일기장'은 일기를 작성하면서 그날의 자기 감정을 표현할 수 있는 서비스이다.
먼저 홈페이지를 준비해야 한다. Home은 앱에 접속할 때 가장 먼저 나오는 페이지로, 보통 '인덱스 페이지'라고 부른다. 이 페이지는 크게 상단 헤더 섹션과 하단 일기 리스트로 구성된다. 헤더 섹션에는 월 단위로 일기를 조회하는 기능이 있으며 이때 좌우 버튼을 클릭해서 월 단위로 날짜를 이동할 수 있다. 일기 리스트 섹션에는 최신순, 오래된 순으로 일기 리스트를 정렬하는 기능과 새로운 일기를 추가할 수 있는 기능이 있다. 또, '새 일기 쓰기' 버튼을 클릭하면 새로운 일기를 작성하는 new 페이지로 이동하는 기능을 구현할 것이다.
그 다음은 new 페이지이다. 이는 새로운 일기를 추가하는 페이지로, '/new' 경로가 될 것이다. 뒤로가기 버튼, 날짜 입력 폼, 감정 이미지 선택 폼, 일기 작성 폼, 취소하기 버튼, 작성 완료 버튼으로 구성된다. 일기를 작성 후 작성 완료 버튼을 클릭하면 home으로 돌아가며 홈페이지의 리스트에 작성한 일기가 추가된다.
Diary 페이지는 작성한 일기를 상세히 조회하는 페이지이다. 상세 또는 콘텐츠 페이지라고도 불리며 경로는 '/diary/(일기)id'가 될 것이다. 뒤로가기, 수정하기 버튼으로 구성되며, 수정하기 버튼을 클릭하면 edit 페이지로 이동된다.
edit 페이지는 작성한 일기를 수정하는 페이지이다. 경로는 '/edit/id'이며 new 페이지와 모습이 유사하나, 헤더의 제목이 달라지고 삭제하기 버튼이 추가된다.
1-(2) 리액트 앱 만들기
새 리액트 앱을 만드는 방법은 프로젝트 1, 2의 과정과 유사하다. 방법을 간단하게 기술하자면,
1. 새 폴더를 만든 후 터미널에서 npx create-react-app . 명령으로 리액트 앱을 생성한다.
2. src 폴더에서 App.test.js, logo.svg, reportWebVitals.js, setupTest.js를 삭제한다.
3. 이후 App.js와 index.js에 있는 불필요한 코드를 삭제한다.
올바르게 준비했다면 코드는 아래와 같아야 한다.
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 />);
4. 마지막으로 npm run start 명령을 입력해서 리액트 앱을 시작한다.
1-(3) 폰트 설정하기
리액트에서 원하는 폰트를 지정할 때는 파일을 다운로드해 프로젝트에 포함하거나 웹에서 불러오는 방법이 있다. 이번에는 특정 URL로 폰트를 가져오는 '웹 폰트' 방식을 이용하겠다. 구글 Fonts에 접속하여 쓰고자 하는 폰트를 불러와서 Get font를 누른다.
그 다음 get embed code 버튼을 클릭해서 나오는 창에서 '@import' 부분을 누른다. 그러면 웹 폰트 주소를 볼 수 있고, 그대로 복사하여 index.css 스타일 규칙에 적용한다.
1-(4) 이미지 준비하기
이제 사용할 이미지를 다운로드해서 페이지에 렌더링 할 차례이다. 감정 이미지는 1번부터 5번이 있으며 차례대로, 완전 좋음, 좋음, 그럭저럭, 나쁨, 끔찍함이 있다. src 폴더에 img 폴더를 생성한 뒤 다운 받은 이미지를 파일명 그대로 저장한다. 이미지를 불러올 때는 다음과 같은 코드를 통해 테스트 해볼 수 있다.
import emotion1 from "./img/emotion1.png";
function App() {
return (<div className="App">
<img alt="감정1" src={emotion1} />
</div>
);
}
하지만 이처럼 매번 일일이 불러오는 방식은 불편하다. 이미지 번호에 맞게 적절한 이미지를 반환하는 함수를 만들어서 보다 편리한 코드를 만들어보자. 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를 호출해 모든 감정 이미지를 페이지에 렌더링한다.
이 과정을 글로 간단히 설명하자면, 다음과 같다.
1. 함수 getEmotionImgById의 매개변수 emotionId에는 페이지나 컴포넌트에서 전달된 감정 이미지 번호가 저장된다.
2. 이때, String 메서드를 이용해 명시적으로 형을 변환하고 switch 문으로 번호와 일치하는 이미지를 찾아 반환한다.
3. 이후 App.js에서는 함수 getEmotionImgById를 util.js에서 불러와서 <img> 태그의 src 속성에 함수를 설정하고, 인수로 감정 이미지 번호를 전달한다.
라우팅이란, 경로를 의미하는 Route와 진행을 뜻하는 ing가 합쳐진 단어로, '경로를 지정하는 과정' 이라는 뜻이다. 데이터는 여러 개의 라우터를 거쳐 전송되게 되는데, 이 라우터가 현재 위치에서 가장 빨리 이동하는 경로를 찾아 데이터를 보내주게 된다. 즉, 결국 라우팅은 데이터 전달을 목적으로 최적의 경로를 찾아 데이터를 전송하는 과정이라고 정의할 수 있다.
2-(1) 리액트의 페이지 라우팅
페이지 라우팅은 요청에 따라 적절한 페이지를 반환하는 일련의 과정이다. 이때 페이지 라우팅의 구현은 웹 페이지를 어디서 만드느냐에 따라 서버 사이드 렌더링과 클라이언트 사이드 렌더링 두 가지로 구분한다. 리액트는 브라우저에서 페이지를 만드는 '클라이언트 사이드 렌더링' 방식을 채택했다.
우선 서버 사이드 렌더링부터 알아보자. 서버 사이드 렌더링은 다음과 같이 작동한다.
1. 웹 브라우저에서 -/blog라는 URL로 서비스를 요청한다.
2. 웹 서버는 요청 URL에서 경로 블로그를 확인하고 blog.html을 생성해 반환한다.
3. 웹 브라우저는 웹 서버에서 반환된 blog.html을 보여준다.
만일 사용자가 버튼 또는 링크를 통해 페이지를 이동하면 다음과 같이 작동한다.
1. 웹 브라우저에서 -/books로 서비스를 요청한다.
2. 웹 서버는 요청 URL에서 경로 books를 확인하고 books.html을 생성해 반환한다.
3. 웹 브라우저는 웹 서버가 반환한 books.html을 보여준다. 이때 새로고침이 발생된다.
이처럼 서버 사이드 렌더링은 검색 엔진을 최적화하며 처음 접속할 때 속도가 빠르다는 장점이 있다. 그러나 페이지를 이동할 때마다 서버가 새로운 페이지를 생성해 제공하기 때문에 많은 연산이 필요하다. 그렇다보니 동시 요청이 많아지는 서비스라면 서버에 부하가 걸릴 수 있다. 페이지를 이동할 때마다 브라우저는 서버가 제공하는 페이지를 기다려야 하기 때문에 속도가 느려진다는 단점이 있다.
이제는 클라이언트 사이드 렌더링에 대해서 알아보자. 다음과 같이 동작한다.
1. 웹 브라우저가 -/blog로 서비스를 요청한다.
2. 웹 서버는 요청 URL의 경로를 따지지 않고 페이지의 틀 역할을 하는 index.html과 자바스크립트 애플리케이션인 리액트 앱을 함께 반환한다.
3. 웹 브라우저는 서버에서 제공된 index.html 페이지를 보여주고 자바스크립트로 이루어진 리액트 앱을 실행한다. 이 리액트 앱은 현재 경로에 맞는 페이지를 보여준다.
4. 사용자가 페이지를 이동하면 웹 브라우저는 서버에서 받은 리액트 앱을 실행해 자체적으로 페이지를 교체한다.
이 방식의 핵심은 사용자가 보는 페이지를 웹 서버가 아닌 브라우저가 완성한다는 것이다. 브라우저는 처음 접속할 때만 서버에게 데이터를 요청하며, 페이지를 이동할 때는 별도의 요청을 하지 않는다.
클라이언트 사이드 렌더링에서는 서버가 html 파일과 자바스크립트 애플리케이션을 함께 제공하기 때문에, 처음 사이트에 접속할 때는 비교적 느릴 수 있다. 하지만 페이지를 이동할 때는 브라우저에서 페이지를 직접 교체하므로 속도가 훨씬 빠르다는 장점이 있다.
2-(2) 리액트 라우터로 페이지 라우팅하기
리액트 라우터란, Remix 팀에서 제작한 오픈소스 라이브러리이다. 이 라우터를 이용해서 적은 줄의 코드로 리액트 앱을 간단히 구축할 수 있다. 리액트 라우터를 설치하려면 다음과 같은 명령어를 입력하면 된다.
npm i react-router-dom
리액트 라우터의 버전은 package.json 의 "dependencies"에서 확인할 수 있다. 설치한 라우터를 프로젝트에 적용하려면 BrowserRouter 컴포넌트로 App을 감싸면 된다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>);
BrowserRouter에는 브라우저의 주소 변경을 감지하는 기능이 있다.
2-(3) 페이지 컴포넌트 만들기
src 아래에 pages 폴더를 생성해서 Home, New, Diary, Edit 페이지를 생성한다. 그리고 각각 페이지에 다음과 같이 코드를 작성한다.
const Edit = () => {
return <div>Edit페이지입니다</div>
}
export default Edit;
2-(4) 페이지 라우팅 구현하기
URL 경로에 따라 브라우저에 적절한 페이지를 렌더링하도록 페이지 라우팅을 구현할 것이다. 다음과 같이 App.js의 코드를 수정한다.
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>
코드를 간단히 설명하자면, 일단 Routes와 Route 컴포넌트를 불러오고, 페이지 역할을 담당할 4개의 컴포넌트를 불러온다. Routes는 여러 Route 컴포넌트를 감싸도록 설정하고 그 안에 URL 경로에 맞게 적절한 컴포넌트를 페이지에 렌더링한다. 이 Routes 문은 자바스크립트의 switch 문과 유사하다. Routes는 자신이 감싸는 Route 컴포넌트 중에서 브라우저 주소 표시줄에 입력된 URL 경로와 일치하는 요소를 찾아 페이지에 렌더링한다.
2-(5) 페이지 이동 구현하기
경로로 분리된 페이지 간의 이동 방법에 대해서 알아보자. 리액트 라우터로 페이지 라우팅을 구현하는 앱에서는 Link라는 컴포넌트를 이용한다.
<Link to='이동할 경로'>링크이름</Link>
Link 컴포넌트는 to 속성에 경로를 지정해 페이지를 이동하게 된다. App.js에서 Link컴포넌트를 이용해 페이지를 이동하도록 링크를 추가한다.
(...)
<div>
<Link to={"/"}>Home</Link>
<Link to={"/new"}>New</Link>
<Link to={"/diary"}>Diary</Link>
<Link to={"/edit"}>Edit</Link>
</div>
react-router-dom 라이브러리에서 Link 컴포넌트를 불러와서 Link 컴포넌트 4개를 배치해 감정 일기장의 모든 페이지로 이동할 수 있는 링크를 만든 코드이다.
이번 파트에서는 리액트 라우터를 이용한 동적 경로 라우팅에 대해 알아보도록 하겠다. 동적 경로란, 특정 아이템을 나타내는 id처럼 값이 변하는 요소를 URL에 포함하는 경우를 말한다.
3-(1) 동적 경로의 종류
동적 경로를 표현하는 방법에는 URL 파라미터와 쿼리 스트링 두 가지가 있다.
1. URL 파라미터
-> URL에 유동적인 값을 넣는 방법이다. id가 3인 일기 상세 페이지의 URL은 다음과 같이 이용할 수 있다.
https://localhost:3000/diary/3
주로 id나 이름을 이용해 특정 데이터를 조회할 때 사용한다.
2. 쿼리 스트링
-> 쿼리 스트링은 물음표 뒤에 key=value 문법으로 URL에 유동적인 값을 포함하는 방법이다.
https://localhost:3000?sort=latest
만약 유동적인 값을 두 개 이상 포함해야 한다면 &로 구분한다.
https://localhost:3000?sort=latest&page=1
3-(2) 동적 경로에 대응하기
이 프로젝트에서도 동적 경로를 사용한다. Diary 페이지는 특정 id를 가진 일기를 상세 조회할 때 사용한다. 이 페이지로 이동하기 위해, URL 파라미터 방식으로 어떤 일기 아이템을 조회할지 경로를 알려줘야 한다.
<Route path="/diary/:id" element={<Diary />} />
diary/:id 형식의 동적 경로를 작성하면, 이제 경로가 유동적인 Diary 컴포넌트를 페이지에 렌더링할 수 있게 된다.
3-(3) URL 파라미터 값 불러오기
이제 URL 파라미터로 전달한 일기 id를 불러와 Diary페이지에서 사용해보자.
import {useParams} from "react-router-dom";
const Diary = () => {
const params = useParams();
console.log(params);
코드를 간단히 설명하자면, react-router-dom 라이브러리에서 useParams를 불러오고, useParams 훅을 호출한다. 이후 불러온 현재 경로의 URL 파라미터를 콘솔에 출력하도록 하였다.
Diary 컴포넌트를 다음과 같이 수정한다.
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 파라미터 객체에서 id 프로퍼티 값을 구조분해 할당한 후 불러온 id 값을 페이지에 렌더링하였다.
3-(4) 쿼리 스트링으로 값 불러오기
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;
react-router-dom에서 useSearchParams 훅을 불러온 뒤 호출한다. 이 훅은 useState처럼 배열 형태로 값을 반환한다. 이후 useSearchParams가 반환한 첫 번째 요소에서 sort 값을 불러와 콘솔에 출력한다. get 메서드를 이용한다.
이제 아래 사진과 같이 쿼리 스트링으로 설정한 sort 값을 제대로 불러오는지 콘솔에서 확인할 수 있다.
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).
Corner React.js 1
Editor: Ijin
[React.js 1팀] 9장 컴포넌트 트리에 데이터 공급하기 (0) | 2025.01.17 |
---|---|
[React.js 1팀] 8장. 최적화 (0) | 2025.01.10 |
[React.js 1팀] 7장. useReducer와 상태 관리 (0) | 2025.01.10 |
[React.js 1팀] Project 2 [할 일 관리] 앱 만들기 2 (Read: 할 일 리스트 렌더링하기 ~ Delete: 할 일 삭제하기) (0) | 2025.01.03 |
[React.js 1팀] Project2 [할 일 관리] 앱 만들기 (0) | 2024.12.27 |