요구사항 분석하기 → 리액트 앱 만들기 → 폰트 설정하기 → 이미지 준비하기 순으로 진행한다.
어떤 페이지가 있고 무슨 기능이 있는지 알아보도록 하자.
Home 페이지
사용자가 앱에 접속하면 처음으로 만나는 페이지다. 인덱스 페이지라고도 부르며 경로는 ‘/’다.
New 페이지
새로운 일기를 추가하는 페이지이며 경로는 ‘/new’이다.
Diary 페이지
작성한 일기를 상세히 조회하는 페이지며 경로는 ‘/diary/id’이다. Home에서 일기 리스트를 조회한 다음, 특정 일기를 클릭하면 Diary 페이지로 이동한다. 이런 페이지를 상세 또는 콘텐츠 페이지라고 한다.
Edit 페이지
일기를 수정하는 페이지이며 경로는 ‘/edit/id’이다. Home 또는 Diary페이지에서 <수정하기> 버튼을 클릭하면 Edit 페이지로 이동한다. New 페이지와 유사하며 <삭제하기> 버튼이 추가되었다.
[감정 일기장] 프로젝트를 위한 새 리액트 앱을 만들고 불필요한 파일과 코드를 삭제한다.
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
리액트 앱에서 사용자가 원하는 폰트를 지정할 때는 파일을 다운로드해 프로젝트에 포함하거나 웹에서 불러오는 방법이 있다. 이 프로젝트에서는 특정 URL로 폰트를 가져오는 ‘웹 폰트’ 방식을 이용한다.
구글 Fonts에 접속해 폰트 가져오기
해당 주소로 접속해 ‘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;
}
감정 이미지 다운로드하기
https://github.com/winterlood/one-bite-react/releases/tag/emotion
다음 링크로 접속해 프로젝트 src/img 폴더에 파일명 그대로 저장한다.
이미지를 불러오는 함수 만들기
이미지 번호에 맞게 적절한 이미지를 반환하는 함수를 만드는게 필요하다. 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); // 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;
}
}
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) 서버 사이드 렌더링
페이지 라우팅은 다음과 같이 동작한다.
사용자가 버튼 또는 링크를 클릭해 페이지를 이동할 때는 다음과 같이 동작한다.
서버 사이드 렌더링은 이처럼 웹 브라우저에 표시할 페이지를 웹 서버에 만들어 전달한다.
2) 클라이언트 사이드 렌더링
리액트 앱은 html 파일이 하나뿐인 단일 페이지 애플리케이션이므로 클라이언트 사이드 렌더링으로 페이지를 라우팅한다.
이는 페이지를 브라우저가 직접 만드는데 다음과 같이 동작한다.
클라이언트 사이드 렌더링의 핵심은 사용자가 보는 페이지를 웹서버가 아닌 브라우저가 완성한다는 점이다. 브라우저는 처음 접속할 때만 서버에게 데이터를 요청하며, 페이지를 이동할 때는 별도의 요청을 하지 않는다.
이미지 비교
리액트 라우터(React Router)는 Remix 팀에서 제작한 오픈소스 라이브러리이다. 이 페이지 라우팅 전용 라이브러리를 이용하여 필요한 기능을 손쉽게 구현할 수 있다.
npm i react-router-dom
☑️ 참고
react-router를 핵심 코드로 하여 웹용(react-router-dom)과 앱용(react-router-native)을 함께 릴리스한다.
"dependencies": {
"react-router-dom": "^6.21.1",
},
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
페이지 역할을 담당할 컴포넌트는 별도의 폴더(src/pages)로 분리하여 차례대로 작성한다.
const Home = () => {
return (
<div>Home 페이지</div>
);
};
export default Home;
나머지 컴포넌도 이와 같은 형식으로 작성한다.
URL 경로에 따라 브라우저에 적절한 페이지를 렌더링하도록 페이지 라우팅을 구현한다. react-router-dom이 제공하는 Routes와 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 컴포넌트를 사용한다.
⇒ 브라우저에서 컴포넌트만 교체하는 식으로 렌더링하므로 이동 속도가 매우 빠르다.
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 파라미터 방식으로 전달해야 한다.
<Route path="/diary/:id" element={<Diary />} />
URL 파라미터 값 불러오기
앞서 URL 파라미터로 전달한 일기 id를 불러와 Diary 페이지에서 사용하도록 작성한다. 이때 react-router-dom이 제공하는 리액트 훅 useParams를 이용한다.
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-dom의 useSearchParams 훅을 이용하여 URL에 있는 쿼리 스트링 값을 꺼내 사용할 수 있다.
import { useSearchParams } from "react-router-dom";
const Home = () => {
const [searchParams, setSearchParams] = useSearchParams();
console.log(searchParams.get("sort"));
return (
<div>Home 페이지</div>
);
};
export default Home;
useSearchParams은 useState처럼 배열 형태로 값을 반환한다.
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
[리액트 스타터3] 16장 리덕스 라이브러리 이해하기 17장 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 (1) | 2024.01.19 |
---|---|
[리액터 스타터3] 9장 컴포넌트 트리에 데이터 공급하기 (1) | 2024.01.05 |
[리액터 스타터3] 7장 useReducer와 상태 관리 / 8장 최적화 (0) | 2023.12.29 |
[리액터 스타터3] project 2. [할 일 관리] 앱 만들기 2 (1) | 2023.12.22 |
[리액터 스타터3] project 2. [할 일 관리] 앱 만들기 1 (1) | 2023.12.01 |