Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jeonggoncho/react-practice-emotion-diary
๐ React๋ฅผ ์ด์ฉํ์ฌ ์ ์ํ 'Emotion Diary ํด๋ก ์ฝ๋ฉ'์
๋๋ค.
https://github.com/jeonggoncho/react-practice-emotion-diary
clone-coding javascript jsx react
Last synced: 19 days ago
JSON representation
๐ React๋ฅผ ์ด์ฉํ์ฌ ์ ์ํ 'Emotion Diary ํด๋ก ์ฝ๋ฉ'์ ๋๋ค.
- Host: GitHub
- URL: https://github.com/jeonggoncho/react-practice-emotion-diary
- Owner: JeonggonCho
- Created: 2024-01-28T13:44:12.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2024-02-29T05:41:42.000Z (10 months ago)
- Last Synced: 2024-12-20T06:07:49.022Z (19 days ago)
- Topics: clone-coding, javascript, jsx, react
- Language: JavaScript
- Homepage:
- Size: 7.01 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๊ฐ์ฑ ์ผ๊ธฐ์ฅ ๋ง๋ค๊ธฐ
๋ณธ ํ๋ก์ ํธ๋ ["ํ์ ํฌ๊ธฐ๋ก ์๋ผ ๋จน๋ ๋ฆฌ์กํธ ๊ฐ์"](https://www.udemy.com/course/winterlood-react-basic/)์ "๊ฐ์ฑ ์ผ๊ธฐ์ฅ ๋ง๋ค์ด ๋ณด๊ธฐ"๋ฅผ ํด๋ก ํ ํ๋ก์ ํธ๋ก "๋ฆฌ์กํธ"์
๊ธฐ์ด ์ง์์ ํ์ตํ๋ ๊ฒ์ ๋ชฉํํ์์ต๋๋ค.
## ๋ชฉ์ฐจ
1. [ํ์ด์ง ๋ผ์ฐํ - React SPA & CSR](#1-ํ์ด์ง-๋ผ์ฐํ ---react-spa--csr)
2. [ํ์ด์ง ๋ผ์ฐํ - React Router ๊ธฐ๋ณธ](#2-ํ์ด์ง-๋ผ์ฐํ ---react-router-๊ธฐ๋ณธ)
3. [ํ์ด์ง ๋ผ์ฐํ - React Router ์์ฉ](#3-ํ์ด์ง-๋ผ์ฐํ ---react-router-์์ฉ)
4. [ํ๋ก์ ํธ ๊ธฐ์ด๊ณต์ฌ 1](#4-ํ๋ก์ ํธ-๊ธฐ์ด๊ณต์ฌ-1)
5. [ํ๋ก์ ํธ ๊ธฐ์ด๊ณต์ฌ 2](#5-ํ๋ก์ ํธ-๊ธฐ์ด๊ณต์ฌ-2)
6. [ํ์ด์ง ๊ตฌํ](#6-ํ์ด์ง-๊ตฌํ)
7. [๋ฒ๊ทธ](#7-๋ฒ๊ทธ)
8. [LocalStorage](#8-localstorage)
9. [์ต์ ํ](#9-์ต์ ํ)
10. [๋ฐฐํฌ ์ค๋น & ํ๋ก์ ํธ ๋น๋](#10-๋ฐฐํฌ-์ค๋น--ํ๋ก์ ํธ-๋น๋)
11. [Firebase ๋ฐฐํฌ](#11-firebase-๋ฐฐํฌ)
12. [Open Graph](#12-open-graph)
## 1. ํ์ด์ง ๋ผ์ฐํ - React SPA & CSR
### 1-1. Page Routing
### - Routing
- ์ด๋ค ๋คํธ์ํฌ ๋ด์์ ํต์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ `๊ฒฝ๋ก๋ฅผ ์ ํ`ํ๋ ์ผ๋ จ์ ๊ณผ์
- `Router` : ๋ฐ์ดํฐ์ ๊ฒฝ๋ก๋ฅผ ์ค์๊ฐ์ผ๋ก ์ง์ ํด์ฃผ๋ ์ญํ ์ ํ๋ ๊ฒ![๋ผ์ฐํ ](README_img/React_Router.png)
<๋คํธ์ํฌ ํต์ ์, ๋ผ์ฐํฐ>
### - Page Routing์ด๋?
- ๋ธ๋ผ์ฐ์ ์์ ๋ณด๋ธ `์์ฒญ`์ ๋ฐํ์ผ๋ก ์๋ง์ `ํ์ด์ง(ํ ํ๋ฆฟ)`์ ์น ์๋ฒ์์ `์๋ต`ํ๋ ๊ฒ
- ์๋ก์ด ์์ฒญ๋ง๋ค ์๋ก์ด ํ์ด์ง๋ฅผ ์๋ตํ๊ฒ ๋๊ณ ๊ทธ ๊ณผ์ ์์ ๋ธ๋ผ์ฐ์ ๋ `์๋ก๊ณ ์นจ(ํ๋ฉด๊น๋ฐ์)`์ด ๋ฐ์ํ๊ฒ ๋จ![MPA ํ์ด์ง ๋ผ์ฐํ ](README_img/React_page_routing.png)
- `MPA(Multi page Application)` : ์์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด `์ฌ๋ฌ ๊ฐ์ ํ์ด์ง`๋ฅผ ์ค๋นํด๋์๋ค๊ฐ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๊ฒฝ๋ก์ ๋ฐ๋ผ ์ ์ ํ ํ์ด์ง๋ฅผ ์๋ตํ๋ ๋ฐฉ์
### 1-2. SPA(Single page Application)
- React๋ MPA์ด ์๋ `SPA(Singlepage Application`์ ๋ฐฉ์์ ์ฌ์ฉ
- ์ด๋ ํ ์์ฒญ์ด ์ฃผ์ด์ ธ๋ `๋จ์ผํ ํ์ด์ง`๋ฅผ ์๋ตํจ
- ์์ฒญ๋ง๋ค ์๋ก๊ณ ์นจ์ด ๋ฐ์ํ์ง ์์ ํ์ด์ง์ ์ด๋์ด `๋น ๋ฅด๊ณ ์พ์ ํจ`![SPA ํ์ด์ง ๋ผ์ฐํ ](README_img/React_page_routing_SPA.png)
### - SPA ์๋ ์๋ฆฌ
![SPA ์๋](README_img/React_SPA_action.png)
1. ์ด๊ธฐ์ ์น ์๋ฒ์์ `๋จ์ผ ํ์ด์ง`์ ํจ๊ป `React app`์ ์ ๋ฌํด์ค
2. ์ดํ, ์ ๋ฐ์ดํธ ์, ์น ์๋ฒ๊ฐ ์๋ ๋ธ๋ผ์ฐ์ ์ `React app`์์ `ํ์ด์ง๋ฅผ ์ ๋ฐ์ดํธ` ํด์ฃผ๊ธฐ ๋๋ฌธ์ ๋น ๋ฆ(ํด๋ผ์ด์ธํธ ์ธก์์ ์ฒ๋ฆฌ)
3. ์๋ฒ์ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์์ ์ ์์
4. ํ์ง๋ง, ๋ฐ์ดํฐ๊ฐ ๋ณํ๊ฑฐ๋, ์๋ก์ด `๋ฐ์ดํฐ๊ฐ ํ์`ํ ๊ฒฝ์ฐ์๋ ์ผ๋จ ํ๋ฉด์ ์ ๋ฐ์ดํธํ๊ณ `๋ฐ์ดํฐ๋ง ์๋ฒ์์ ์๋ต`๋ฐ์ ์ฑ์ฐ๊ฒ ๋จ
### 1-3. CSR (Client Side Rendering)
- `ํด๋ผ์ด์ธํธ ์ธก`์์ `ํ๋ฉด์ ๋ ๋๋ง`ํ๋ ๊ฒ
## 2. ํ์ด์ง ๋ผ์ฐํ - React Router ๊ธฐ๋ณธ
### 2-1. ํ๋ก์ ํธ ์์ฑ
- `npx create-react-app emotion-diary`๋ก React ์ฑ ์์ฑ
### 2-2. React Router
- React์์ Page Routing์ ํ๊ธฐ ์ํด์๋ `React Router`๊ฐ ํ์ํจ
- `React Router` : ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ ๋๋ง(CSR)์ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- [React Router ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ณต์ ์ฌ์ดํธ](https://reactrouter.com/en/main)
### - React Router ์ค์น
```bash
$ npm install react-router-dom@(๋ฒ์ )
```
![react router ์ค์น ํ์ธ](README_img/React_react_router_package.png)
### - Router๋ก ๊ด๋ฆฌํ ํ์ด์ง ๋ชจ์ ํด๋ ๋ฐ ํ์ด์ง ์์ฑ
- src/`pages` ํด๋ ์์ฑ
- ํด๋ ์์ `Home.js`, `New.js`, `Edit.js`, `Diary.js` ํ์ผ ์์ฑ
```jsx
// src/pages/Home.jsconst Home = () => {
return (
Home
์ด๊ณณ์ ํ ์ ๋๋ค.
);
};export default Home;
// ๋ค๋ฅธ ํ์ด์ง๋ค๋ ๋์ผํ๊ฒ ์ธํ
```- ๊ฐ ํ์ด์ง ์ปดํฌ๋ํธ๋ก ๊ตฌ์ฑ
### - App ์ปดํฌ๋ํธ์์ Route ๋ถ๋ฐฐํ๊ธฐ
<๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์ปดํฌ๋ํธ ๊ฐ์ ธ์ค๊ธฐ>
```jsx
// src/App.jsimport {BrowserRouter, Route, Routes} from "react-router-dom";
import Home from "./pages/Home";
import New from "./pages/New";
import Edit from "./pages/Edit";
import Diary from "./pages/Diary";
```- react-route-dom ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ `BrowserRouter`, `Route`, `Routes` ๊ฐ์ ธ์ค๊ธฐ
- pages ํด๋์์ ์์ฑํ ๊ฐ ํ์ด์ง ์ปดํฌ๋ํธ ๊ฐ์ ธ์ค๊ธฐ
```jsx
// src/App.jsreturn (
App.js
}/>
}/>
);
```- `BrowserRouter` : React Router๋ฅผ ์ ์ฉํ `์ ์ฒด App` ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๊ธฐ
- `Routes` : Route ์ ์ฉํ `์ปดํฌ๋ํธ๋ค` ๋ฌถ๊ธฐ
- `Route` : ๊ฐ๊ฐ์ path(๊ฒฝ๋ก)์ element(์ปดํฌ๋ํธ) `๋งคํ`
- ๋ฐ๋ผ์ ๋งคํ๋ ์ปดํฌ๋ํธ๋ ํด๋น url ๊ฒฝ๋ก์ธ ๊ฒฝ์ฐ์๋ง ๋ ๋๋ง ๋จ![๊ธฐ๋ณธ index ํ๋ฉด](README_img/React_basic_path.png)
![new ๊ฒฝ๋ก ํ๋ฉด](README_img/React_new_path.png)
### - Base Template
- Routes ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ์ง ์์ `
App.js
`์ ๊ฒฝ์ฐ ๊ณ์ ๋ ๋๋ง๋๋ ๊ฒ์ ์ ์ ์์
- ๋ฐ๋ผ์ Base Template์ผ๋ก `๊ณ์ ํ๋ฉด์ ์ถ๋ ฅ`๋์ด์ผํ๋ header, navbar, footer๋ `Routes์ ๊ฐ์ธ์ง ์์`์ผ๋ก์จ ๊ณ์ ํ๋ฉด์ ์ถ๋ ฅ๋๋๋ก ํ ์ ์์
### - ํ๋ฉด ์ด๋์ํค๋ ์์ ๋ง๋ค๊ธฐ Link
<๊ธฐ์กด a ํ๊ทธ๋ก ๋งํฌ ์์ฑ>
```jsx
// src/App.jsNew๋ก ์ด๋
```- a ํ๊ทธ๋ฅผ ์ด์ฉํ์ฌ ๋งํฌ๋ฅผ ๋ง๋ค๊ณ href ์์ฑ์ผ๋ก ๊ฒฝ๋ก ์ฐ๊ฒฐ
- ํด๋น ๋งํฌ ํด๋ฆญ ์, ํด๋น url๋ก ์ด๋์ ํ์ง๋ง `์๋ก๊ณ ์นจ`์ด ๋ฐ์๋จ(MPA์ฒ๋ผ ์๋ํจ)
- components ํด๋ ๋ง๋ค๊ณ ํ ์คํธ์ฉ ์ปดํฌ๋ํธ ์์ฑ
```jsx
// src/components/RouterTest.jsimport {Link} from "react-router-dom";
const RouteTest = () => {
return (
HOME
DIARY
NEW
EDIT
);
};export default RouteTest;
```- `react-router-dom` ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ `Link` ๊ฐ์ ธ์ค๊ธฐ
- Link์ `to ์์ฑ`์ `๊ฒฝ๋ก(path)` ๋งคํํ๊ธฐ
- ํด๋น ์ปดํฌ๋ํธ ๋ด๋ณด๋ด๊ธฐ
```jsx
// src/App.jsimport RouteTest from "./components/RouteTest";
App.js
}/>
}/>
}/>
}/>
```
- ํด๋น ์ปดํฌ๋ํธ App ์ปดํฌ๋ํธ์ ๊ฐ์ ธ์ค๊ธฐ
- ์ด๋ ๊ฒ Link ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด SPA๋ก ๋์ํ๋ฉฐ ํ๋ฉด ์ด๋ ์, ์๋ก๊ณ ์นจ์ด ๋์ง ์์
### 2-3. React Router ์ ๋ฆฌ
- ํด๋น ๊ฒฝ๋ก๋ง๋ค ๋ ๋๋ง ๋ ํ์ด์ง๋ฅผ ๋งคํํด์ฃผ๊ฒ ๋๋ฉด url์ ๋ฐ๋ผ์ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํจ
- ์ค์ ๋ก๋ ์ด๋ํ๊ธฐ๋ณด๋ค๋ ์ฃผ์ด์ง `index ๋จ์ผ ํ์ด์ง`์์ `url`์ ๋ฐ๋ผ์ `์ปดํฌ๋ํธ๋ค์ ๊ต์ฒด`ํด์ค๋ค๊ณ ์ดํดํ ์ ์์
- ํ๋ฉด ์ ํ์ ์๋๊ฐ ๋งค์ฐ ๋น ๋ฆ
## 3. ํ์ด์ง ๋ผ์ฐํ - React Router ์์ฉ
### 3-1. ํ์ต๋ชฉํ
- `useParams` : Path Variable(๊ฒฝ๋ก ๋ณ์)
- `useSearchParams` : Query String(url๋ก ๋ฐ์ดํฐ ์ ๋ฌ)
- `useNavigate` : Page Moving(Link๊ฐ ์๋ ํจ์๋ก ํ์ด์ง ์ด๋)
### 3-2. Path Variable & useParams
### - Diary ํ์ด์ง
- ๊ฒฝ๋ก : '/diary'
- ํน์ง : ํน์ ์ผ๊ธฐ์ ์์ธ ํ์ด์ง๋ก์ ์ด๋ค ์ผ๊ธฐ๋ฅผ ๋ณด์ฌ์ฃผ์ด์ผ ํ ์ง ์ ๋ฌ ๋ฐ์์ผํจ
- ex) /diary/1 -> 1๋ฒ ์ผ๊ธฐ
### - Path Variable
```jsx
// App.js
App.js
}/>
}/>
}/>
}/>
```
- diary ๊ฒฝ๋ก ๋ค์ `:id`๋ฅผ ํฌํจ์ํด
- ๊ฐ id ๊ฐ์ ๋ฐ๋ผ์ diary ๊ฒฝ๋ก๋ฅผ ๋ถ๋ฐฐ์ํด
### - useParams
```jsx
// pages/Diary.jsimport {useParams} from "react-router-dom";
const Diary = () => {
const {id} = useParams();
console.log(id);
};
```- React ์์ฒด์์๊ฐ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ `์ปค์คํ Hooks`์ธ `useParams` ๊ฐ์ ธ์ค๊ธฐ
- id ๊ฐ์ useParams๋ก ์ค์ ํ๋ฉด ํด๋น id ๊ฐ์ Diary ํ์ด์ง์์ ์ด์ฉํ ์ ์์
![useParams](README_img/React_useParams.gif)
### 3-3. Query String & useSearchParams
### - Query String
```
- Query ์ ๋ฌ ์์URL : https://site.com/edit?id=10&mode=dark
```- `Query` : ์น ํ์ด์ง์์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ
- `Query String` : url์์ ๋ฌผ์ํ ํค์๋('?') ๋ค์ ์ ๋ฌ๋๋ ๋ฐ์ดํฐ
- `name=value`์ ํํ๋ก '&'๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฌถ์
### - Query String์ path ์ค์ ์ํจ
- ์์ `Path Variable`์ ๊ฒฝ์ฐ, `/diary/:id`์ ๊ฐ์ด ๋ฐ์ Path Variable์ ๊ฒฝ๋ก์ ์ค์ ํด์ฃผ์์
- ๋ฐ๋ฉด, `Query String`์ ๊ฒฝ์ฐ, ๊ฒฝ๋ก์ ๋ณ๋์ ์ค์ ์ ํ์ง ์์๋ ๋จ
### - useSearchParams
```jsx
// pages/Edit.jsimport {useSearchParams} from "react-router-dom";
const Edit = () => {
const [searchParams, setSearchParams] = useSearchParams()const id = searchParams.get('id');
console.log('id : ', id);const mode = searchParams.get('mode');
console.log('mode : ', mode);...
{
setSearchParams({who: '์กฐ์ ๊ณค'})
}}>QS ๋ฐ๊พธ๊ธฐ
};
```- `useSearchParams` : ์ปค์คํ Hooks ์ค ํ๋๋ก useState์ ์ ์ฌํ๊ฒ ๋น๊ตฌ์กฐํ ํ ๋น์ ํตํด `searchParams`์ `setSearchParams`๋ฅผ ๋ฐ์
- `searchParams` : ์ ๋ฌ๋ฐ์ Query String์ด ๊ฐ์ฒด๋ก ๋ด๊ฒจ ์์ผ๋ฉฐ, `.get()` ๋ฉ์๋๋ฅผ ํตํด ๋ฐ์ดํฐ ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์์
- `setSearchParams` : searchParams `๊ฐ์ฒด๋ฅผ ์ ๋ฐ์ดํธ`ํ๋ ํจ์
![useSearchParams ์์](README_img/React_useSearchParams.gif)
### 3-4. Page Moving & useNavigate
### - useNavigate
```jsx
// pages/Edit.jsimport {useNavigate, useSearchParams} from "react-router-dom";
const Edit = () => {
const navigate = useNavigate();...
// ๊ฒฝ๋ก ํ์ด์ง๋ก ์ด๋
{
navigate('/home')
}}>HOME์ผ๋ก ๊ฐ๊ธฐ
// ํด๋น ์ซ์๋งํผ ๋ค๋ก๊ฐ๊ธฐ
{
navigate(-1)
}}>๋ค๋ก๊ฐ๊ธฐ
}
```- `useNavigate` : ์ปค์คํ Hooks ์ค ํ๋๋ก, `ํ์ด์ง๋ฅผ ์ด๋ํ ์ ์๋ ํจ์`๋ฅผ ๋ฐํํจ, ๋ฐ๋ผ์ ์ด ํจ์๋ฅผ navigate๋ผ๋ ๋ณ์์ ํ ๋น
- ๋ฒํผ onClick์ ์ฝ๋ฐฑํจ์์ navigate ํจ์๋ฅผ ๋ฃ๊ณ `์ธ์๋ก ๊ฒฝ๋ก`๋ฅผ ๋ฐ์ผ๋ฉด ๋ฒํผ ํด๋ฆญ ์, ํด๋น ํ์ด์ง๋ก ์ด๋ํจ
- ๊ฒฝ๋ก ๋์ `-(์ซ์)`๋ฅผ ๋ฃ๊ฒ ๋๋ฉด ์ซ์๋งํผ `๋ค๋ก๊ฐ๊ธฐ` ์ํ
![ํ์ด์ง ์ด๋ ๋ฐ ๋ค๋ก๊ฐ๊ธฐ](README_img/React_useNavigate.gif)
## 4. ํ๋ก์ ํธ ๊ธฐ์ด๊ณต์ฌ 1
### 4-1. ์ธํ ํญ๋ชฉ
- ํฐํธ ์ธํ : Google Web Fonts ์ด์ฉ
- ๋ ์ด์์ ์ธํ : ๋ชจ๋ ํ์ด์ง์ ๋ฐ์๋๋ ๋ ์ด์์ ์ธํ
- ์ด๋ฏธ์ง ์์ ์ธํ : ๊ฐ์ ํํ ์ด๋ฏธ์ง๋ค์ ํ๋ก์ ํธ์์ ๋ถ๋ฌ์ ์ฌ์ฉํ ์ ์๋๋ก ํ๊ฒฝ ์ธํ
- ๊ณตํต ์ปดํฌ๋ํธ ์ธํ : ๋ชจ๋ ํ์ด์ง์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ๋ฒํผ, ํค๋ ์ปดํฌ๋ํธ ์ธํ
### 4-2. ํฐํธ ์ธํ
- [Google Web Font](https://fonts.google.com/) ์ฌ์ดํธ์์ ์ฌ์ฉํ ํฐํธ๋ฅผ ์ฐพ์ selectํ๊ธฐ
- `@import...` ๊ตฌ๋ฌธ์ App.css ํ์ผ ์๋จ์ ๋ถ์ฌ๋ฃ๊ธฐ
- css๋ก font-family ์์ฑ ์ถ๊ฐ```css
/*src/App.css*/@import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic&family=Noto+Sans+KR&display=swap');
.App {
padding: 20px;
font-family: 'Nanum Gothic', sans-serif;
font-family: 'Noto Sans KR', sans-serif;
}
```
### 4-3. ๋ ์ด์์ ์ธํ
- ๋ชจ๋ ํ์ด์ง์ ๋ฐ์๋๋ ๋ ์ด์์ ์ธํ
```css
/*src/App.css*/@import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic&family=Noto+Sans+KR&display=swap');
body {
background-color: #f6f6f6;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Nanum Gothic', sans-serif;
min-height: 100vh;
margin: 0px;
}@media (min-width: 650px) {
.App {
width: 640px;
}
}@media (max-width: 650px) {
.App {
width: 90vw;
}
}#root {
background-color: white;
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
}.App {
min-height: 100vh;
padding-left: 20px;
padding-right: 20px;
}
```- ๊ธฐ๋ณธ์ ์ธ ๋ ์ด์์ ์ธํ ์งํ
### 4-4. ์ด๋ฏธ์ง ์์ ์ธํ
- ๊ฐ์ ํํ ์ด๋ฏธ์ง ์ข ๋ฅ 5๊ฐ์ง (๊ธฐ๋ถ ์ต๊ณ , ์ข์, ๊ทธ๋ญ์ ๋ญ, ๋์จ, ๋์ฐํจ)
- ํด๋น ์ด๋ฏธ์ง ํ์ผ `public/assets`์ ๋ด๊ธฐ| | | | | |
|:-----------------------------------------------------------------:|:-----------------------------------------------------------------:|:-----------------------------------------------------------------:|:-----------------------------------------------------------------:|:-----------------------------------------------------------------:|
| ๊ธฐ๋ถ ์ต๊ณ | ์ข์ | ๊ทธ๋ญ์ ๋ญ | ๋์จ | ๋์ฐํจ |
```tsx
// src/pages/App.js...
return (
App.js
}/>
}/>
}/>
}/>
);
```
### - process.env.PUBLIC_URL
- ํด๋น URL์ ์ด๋์ ์๋์ง ๋ฌด์กฐ๊ฑด `public ๋๋ ํ ๋ฆฌ`๋ฅผ ๋ํ๋
```tsx
```- ๋ฐ๋ผ์ ์ ์ด๋ฏธ์ง ์ปดํฌ๋ํธ์ ํด๋น ๊ฒฝ๋ก๋ `public/assets/emotion1.png`๋ฅผ ๊ฐ๋ฆฌํด
- ๋ง์ฝ ๊ฒฝ๋ก๊ฐ public์ผ๋ก ์ฐ๊ฒฐ๋์ง ์์ ๊ฒฝ์ฐ, ์๋์ ๊ฐ์ด ์ด๊ธฐํ ์งํ```tsx
// src/pages/App.jsfunction App() {
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || "";
...
```
### 4-5. ๊ณตํต ์ปดํฌ๋ํธ ์ธํ
- ๋ชจ๋ ํ์ด์ง์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ๋ฒํผ, ํค๋ ์ปดํฌ๋ํธ ์ธํ
- UI ์์๊ฐ `์ด๋ค ๊ธฐ์ค`์ผ๋ก `์ผ๋ง๋งํผ ๋ณํ`ํ๊ฒ ๋๋๊ฐ๋ฅผ `ํจํดํ`ํ๋ ๊ณผ์ ์ด ํ์
### - ๋ฒํผ ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
![๋ฒํผ ์ปดํฌ๋ํธ](README_img/button_components.png)
- ๋ฒํผ ์ปดํฌ๋ํธ ์์ฑ
- props๋ก text, type, onClick์ ๋ฐ์
- btnType ๋ณ์๋ฅผ ํตํด props๋ก ๋ฐ์ type ๊ฐ์ด 'positive', 'negative' ๋ ์ค ํ๋์ด๋ฉด ๊ทธ๋๋ก ์ฌ์ฉํ๊ณ ์ด์ํ ๊ฐ์ด๋ฉด, default๋ก ๋ณํํ๊ธฐ
- ๋ฒํผ ์์์ type์ ๋ฐ๋ผ ๋์ ์ผ๋ก ๋ณํ์ํค๊ธฐ ์ํด `${}` ์ฌ์ฉ
- ์๋ฌด๋ฐ type์ด ์ ๋ฌ ์ ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ์ฌ `defaultProps`๋ก type์ default๋ฅผ ๋ฃ๊ธฐ```tsx
// src/components/MyButton.jsconst MyButton = ({text, type, onClick}) => {
const btnType = ["positive", "negative"].includes(type) ? type : "default";return (
{text}
)
}MyButton.defaultProps = {
type: "default",
}export default MyButton;
```- App์ ๋ฒํผ ์์ฑํ๊ณ Props ์ ๋ฌํ๊ธฐ
- onClick์ผ๋ก alert ๋์ฐ๊ธฐ```tsx
// src/pages/App.js...
alert("๋ฒํผ ํด๋ฆญ")}
type={"positive"}/>
alert("๋ฒํผ ํด๋ฆญ")}
type={"negative"}/>
alert("๋ฒํผ ํด๋ฆญ")}
type={"default"}/>
...
```- css ์คํ์ผ๋ง
```css
/*src/pages/App.css*//* MyButton */
.MyButton {
cursor: pointer;
border: none;
border-radius: 5px;
padding-top: 10px;
padding-bottom: 10px;
padding-right: 20px;
padding-left: 20px;
font-size: 18px;
white-space: nowrap;
font-family: 'Nanum Gothic';
}.MyButton_default {
background-color: #ececec;
color: black;
}.MyButton_positive {
background-color: #64c964;
color: white;
}.MyButton_negative {
background-color: #fd565f;
color: white;
}
```
### - ํค๋ ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
![ํค๋ ์ปดํฌ๋ํธ](README_img/header_component.png)
- ํค๋ ์ปดํฌ๋ํธ ์์ฑ
```tsx
// src/components/MyHeader.jsconst MyHeader = ({headText, leftChild, rightChild}) => {
return
{leftChild}
{headText}
{rightChild}
}export default MyHeader;
```- App.js์ ์ ์ฉ
```tsx
// src/pages/App.js...
alert("์ผ์ชฝ ํด๋ฆญ")}/>}
rightChild={ alert("์ค๋ฅธ์ชฝ ํด๋ฆญ")}/>}
/>
...
```- css ์คํ์ผ๋ง
```css
/*src/pages/App.css*//* HEADER */
header {
padding-top: 20px;
padding-bottom: 20px;
display: flex;
align-items: center;
border-bottom: 1px solid #e2e2e2;
}header > div {
display: flex;
}header .head_text {
width: 50%;
font-size: 25px;
justify-content: center;
}header .head_btn_left {
width: 25%;
justify-content: start;
}header .head_btn_right {
width: 25%;
justify-content: end;
}header button {
font-family: 'Nanum Gothic', sans-serif;
}
```
## 5. ํ๋ก์ ํธ ๊ธฐ์ด๊ณต์ฌ 2
### 5-1. ์ธํ ํญ๋ชฉ
- ์ํ ๊ด๋ฆฌ ์ธํ ํ๊ธฐ : ํ๋ก์ ํธ ์ ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋ ์ผ๊ธฐ ๋ฐ์ดํฐ State ๊ด๋ฆฌ ๋ก์ง ์์ฑํ๊ธฐ
- ํ๋ก์ ํธ State Context ์ธํ ํ๊ธฐ : ์ผ๊ธฐ ๋ฐ์ดํฐ State๋ฅผ ๊ณต๊ธํ Context๋ฅผ ์์ฑํ๊ณ Provider๋ก ๊ณต๊ธํ๊ธฐ
- ํ๋ก์ ํธ Dispatch Context ์ธํ ํ๊ธฐ : ์ผ๊ธฐ ๋ฐ์ดํฐ State์ Dispatch ํจ์๋ค์ ๊ณต๊ธํ Context๋ฅผ ์์ฑํ๊ณ Provider๋ก ๊ณต๊ธํ๊ธฐ
### 5-2. ์ํ ๊ด๋ฆฌ ์ธํ ํ๊ธฐ
![๊ตฌ์กฐ](README_img/project_structure.png)
- App ์ปดํฌ๋ํธ๊ฐ routes๋ก 4๊ฐ์ ํ์ด์ง๋ฅผ ์์์์๋ก ๊ฐ์ง
- URL์ ๋ฐ๋ผ์ ํ์ด์ง๋ฅผ ๋ณด์ฌ์ค
- `/` : Home
- `/new` : New
- `/edit` : Edit
- `/diary` : Diary
### 5-3. ์ํ ๊ด๋ฆฌ ์ธํ ํ๊ธฐ
- `์ผ๊ธฐ ๋ฐ์ดํฐ`๋ `์์ฑ`, `์ญ์ `, `์์ `์ ์ํ ๋ณํ ํจ์๊ฐ ํ์ํจ
- data(์ผ๊ธฐ)๋ฅผ ๊ด๋ฆฌํ๋ ์ฌ๋ฌ ์ํ ๋ณํ ํจ์๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด `useReducer` ์ฌ์ฉ
```javascript
// src/pages/App.jsimport React, {useReducer, useRef} from "react";
...
function App() {
const [data, dispatch] = useReducer(reducer, []);
...
}
```
- reducer ํจ์๋ก ๋ถ๊ธฐ์ฒ๋ฆฌํ๊ธฐ
- dispatch๋ฅผ ํธ์ถํ action ๊ฐ์ฒด์ ํ์ ์ด `INIT`์ผ ๊ฒฝ์ฐ, ๊ฐ์ฒด์ ๋ฐ์ดํฐ ๋ฐํ
- ์์ฑ์ธ `CREATE`์ผ ๊ฒฝ์ฐ, newItem์ newState ๋ฐฐ์ด์ ๋ด์ ์์ฑ
- ์ญ์ ์ธ `REMOVE`์ผ ๊ฒฝ์ฐ, ์ต์ ์ํ state์์ action ๊ฐ์ฒด์ targetId์ ๋ค๋ฅธ ๋ฐ์ดํฐ๋ง ๋ชจ์ ์๋ก์ด ๋ฐฐ์ด newState๋ฅผ ์์ฑ
- ์์ ์ธ `EDIT`์ ๊ฒฝ์ฐ, ์ต์ state์์ action ๊ฐ์ฒด์ id์ ๊ฐ์ ๊ฒฝ์ฐ, action์ data๋ฅผ ๋ฎ์ด์์ฐ๊ณ , ๋ค๋ฅด๋ฉด ๊ทธ๋๋ก์ธ ๋ฐฐ์ด newState๋ฅผ ์์ฑ
- ์ต์ข ์ ์ผ๋ก newState ๋ฐํ```javascript
// src/pages/App.js...
const reducer = (state, action) => {
let newState = [];
switch (action.type) {
case 'INIT': {
return action.data;
}
case 'CREATE': {
const newItem = {
...action.data
};
newState = [newItem, ...state];
break;
}
case 'REMOVE': {
newState = state.filter((it) => it.id !== action.targetId);
break;
}
case 'EDIT': {
newState = state.map((it) =>
it.id === action.data.id ? {...action.data} : it
);
break;
}
default:
return state;
}
return newState;
};
...
```
```javascript
// src/pages/App.js...
function App() {const [data, dispatch] = useReducer(reducer, []);
const dataId = useRef(0);
// CREATE
const onCreate = (date, content, emotion) => {
dispatch({
type: "CREATE",
data: {
id: dataId.current,
date: new Date(date).getTime(),
content,
emotion,
}
});
dataId.current += 1;
};// REMOVE
const onRemove = (targetId) => {
dispatch({
type: "REMOVE",
targetId,
});
};// EDIT
const onEdit = (targetId, date, content, emotion) => {
dispatch({
type: "EDIT",
data: {
id: targetId,
date: new Date(date).getTime(),
content,
emotion,
}
});
};
...
```- dispatch ํธ์ถ ํจ์ ์์ฑํ๊ธฐ
### 5-4. ํ๋ก์ ํธ State Context ์ธํ ํ๊ธฐ
- context ์์ฑ ๋ฐ Provider ์ปดํฌ๋ํธ๋ก ๊ณต๊ธํ๊ธฐ
```javascript
// src/pages/App.js...
export const DiaryStateContext = React.createContext();
...
App.js
}/>
}/>
}/>
}/>
```
### 5-5. ํ๋ก์ ํธ Dispatch Context ์ธํ ํ๊ธฐ
- context ์์ฑ ๋ฐ Provider ์ปดํฌ๋ํธ๋ก ๊ณต๊ธํ๊ธฐ
```javascript
// src/pages/App.js...
export const DiaryDispatchContext = React.createContext();
...
App.js
}/>
}/>
}/>
}/>
```
## 6. ํ์ด์ง ๊ตฌํ
### 6-1. ํ(/)
- ํค๋(์ ๋ณ๊ฒฝ), ํํฐ๋ง(์ผ๊ธฐ ์์ฑ ํฌํจ), ์ผ๊ธฐ(๋ ์ง, ๋ด์ฉ, ๊ฐ์ , ์์ ํ๊ธฐ) ๋ฆฌ์คํธ ๋ถ๋ถ์ผ๋ก ๋๋จ
### - ํค๋
- 3๋จ๊ณ๋ก ๊ฐ๋ฐ ์งํ
1. ๊ฐ์ด๋ฐ ์ ํ์
2. ์ผ์ชฝ ๋ฒํผ
3. ์ค๋ฅธ์ชฝ ๋ฒํผ
## 7. ๋ฒ๊ทธ
## 8. LocalStorage
- ์ผ๊ธฐ๋ฅผ ์์ฑํ๊ณ ์๋ก๊ณ ์นจ ์, ๋๋ฏธ ๋ฐ์ดํฐ ์ธ์ ๋ธ๋ผ์ฐ์ ์์ ์์ฑ๋ ์ผ๊ธฐ ๋ฐ์ดํฐ๋ ์ฌ๋ผ์ง
- ์์ฑ๋ ์ผ๊ธฐ ๋ฐ์ดํฐ๊ฐ ๋ธ๋ผ์ฐ์ ์์์ `ํ๋ฐ์ฑ`์ด๊ธฐ ๋๋ฌธ
- ๋ฐ๋ผ์ ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ฅผ ์ด์ฉํ์ฌ ์ผ๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด ์ค ํ์๊ฐ ์์
- Web์์ ์ ๊ณตํ๋ `Web Storage API`๋ฅผ ์ด์ฉํ ์ ์์[MDN - Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
### 8-1. Web Storage API
- `ํค/๊ฐ`์ ์์ ์ฟ ํค๋ณด๋ค ์ง๊ด์ ์ผ๋ก ์ ์ฅํ ์ ์๋ ๋ฐฉ๋ฒ ์ ๊ณต
- `sessionStorage`์ `localStorage` ๋ฐฉ์์ ์ ๊ณตํจ
### - sessionStorage
- ํ์ด์ง ์ธ์ ์ด ์ ์ง๋๋ ๋์(`๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฆฐ ๋์`) ๋ ๋ฆฝ์ ์ธ ๊ณต๊ฐ์ ์ ๊ณตํจ
- ๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก ์ ์กํ์ง ์์
- ์ ์ฅ๊ณต๊ฐ์ด ์ฟ ํค๋ณด๋ค ํผ(์ต๋ 5MB)
### - localStorage
- sessionStorage์ ๋น์ทํ๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ซ์๋ค๊ฐ `๋ค์ ์ ์ํด๋ ๋ฐ์ดํฐ๊ฐ ๋จ์์์` (๋ฐ์ดํฐ๊ฐ ์ ์ง๋จ)
- ์ ํจ๊ธฐ๊ฐ ์์ด ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํจ
- ๋ธ๋ผ์ฐ์ ์บ์ ๋๋ ๋ก์ปฌ ์ ์ฅ ๋ฐ์ดํฐ๋ฅผ ์ง์์ผ ์ฌ๋ผ์ง1. ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ - setItem(key, value)
```js
// setItem ์์localStorage.setItem("item1", 10);
localStorage.setItem("item2", "20");
localStorage.setItem("item3", JSON.stringify({value: 30}));// ๊ฐ์ผ๋ก ์ซ์, ๋ฌธ์์ด ์ ์ฅ์ด ๊ฐ๋ฅํ๋ฉฐ,
// ๊ฐ์ฒด๋ ๋ธ๋ผ์ฐ์ ๊ฐ ํด์ํ์ง ๋ชปํ๊ธฐ์ JSON.stringify()๋ฅผ ํตํด ์ง๋ ฌํ(๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ณํ)๋ฅผ ํด์ฃผ์ด์ผ ํจ
// JavaScript์์ setItem์ ์ง์๋ localStorage์ ๋ค์ด๊ฐ ๋ฐ์ดํฐ๋ ์ง์ฐ๊ธฐ ์ ๊น์ง ์ ์ง๋จ
```2. ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ - getItem(key)
```js
// getItem ์์const item1 = localStorage.getItem("item1");
const item2 = localStorage.getItem("item2");
const item3 = localStorage.getItem("item3");// ํด๋น ๋ฐ์ดํฐ ํ์ธ
console.log({item1, item2, item3});// ๊ฐ์ ํ์ธํด๋ณด๋ฉด ์ซ์ ํ์ ์ผ๋ก ์ ์ฅํ ๊ฐ์ด ๋ฌธ์์ด๋ก ์ถ๋ ฅ๋จ
// ์ฆ, localStorage์ ๊ฐ์ ๋ชจ๋ ๋ฌธ์์ด๋ก ๋ณํ๋์ด ์ ์ฅ๋จ
// ๋ฐ๋ผ์ ์ซ์์ ๊ฒฝ์ฐ, parseInt() ๋ฑ์ ๋ฉ์๋๋ฅผ ํตํด ์ซ์๋ก ๋ณํํด์ฃผ์ด์ผ ํจ
// ๊ฐ์ฒด๋ JSON.parse()๋ฅผ ํตํด ๊ฐ์ฒด๋ก ๋ณต์ํด์ฃผ์ด์ผ ํจconst item1 = parseInt(localStorage.getItem("item1"));
const item2 = localStorage.getItem("item2");
const item3 = JSON.parse(localStorage.getItem("item3"));
```
## 9. ์ต์ ํ
- ๋ญ๋น๋๋ ์ฐ์ฐ์ ์ฐพ์ ์ต์ ํ ํ๊ธฐ
- ๋ญ๋น๋๋ ์ฐ์ฐ์ฐพ๊ธฐ
- ์ ์ ๋ถ์ ๋ฐฉ๋ฒ : ์์ฑํ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ธฐ
- ๋์ ๋ถ์ ๋ฐฉ๋ฒ : ๋๊ตฌ์ ๋์์ ๋ฐ์ ๋ญ๋น๋๋ ๋ถ๋ถ ์ฐพ๊ธฐ### 9-1. ๋ถํ์ํ ๋ฆฌ๋ ๋
### - Home์ ํํฐ ๋ฐ ์ผ๊ธฐ ์์ฑ
- ์์ ์ด๋ํ ๊ฒฝ์ฐ, ๋ถํ์ํ ๋ฆฌ๋ ๋ ๋ฐ์
- Home์ ์์ด ๋ฐ๋๋ฉด, ์์ ์ปดํฌ๋ํธ์ธ DiaryList๊ฐ ์ํฅ์ ๋ฐ๊ณ DiaryList์ ์์ ์ปดํฌ๋ํธ์ธ ControlMenu๊ฐ ์ํฅ์ ๋ฐ์
- React.memo ์ฌ์ฉ```js
// src/components/DiaryList.js// ControlMenu ์ปดํฌ๋ํธ์ ๊ฐ์ด ๋ฐ๋ ๊ฒฝ์ฐ, ๋ฆฌ๋ ๋๊ฐ ๋๋๋ก React.memo()๋ก ๊ฐ์ธ๊ธฐ
import React, {useState} from "react";
...
const ControlMenu = React.memo(({value, onChange, optionList}) => {
return (
onChange(e.target.value)}
>
{optionList.map((it, idx) => (
{it.name}
))}
)
});
...
```
### - Home์ diaryList
- ์ต์ ์, ์ค๋๋ ์ ๋ฑ์ ํํฐ ๋ณํ์ ๋ฐ๋ผ์ diaryItem๋ค์ ์์น๋ง ๋ณํ๋ฉด ์ข์๋ฐ ๋ชจ๋ ํญ๋ชฉ์ด ๋ฆฌ๋ ๋๋ง ๋จ
- ๋ชจ๋ ๊ฐ์ , ์ข์ ๊ฐ์ , ๋์ ๊ฐ์ ์ผ๋ก ํํฐ ํ ๋, ํญ๋ชฉ์ด ๋ฐ๋์ง ์์๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ ํจ
- ์ผ๊ธฐ ํญ๋ชฉ์ด ๋ง์์ง ์๋ก ์ฌ์ดํธ์ ๊ฒฝํ์ฑ์ด ๋๋น ์ง
- DiaryItem์ React.memo() ์ฌ์ฉ```js
// src/components/DiaryItem.jsimport React from "react";
...
export default React.memo(DiaryItem);
```
### Edit์ DiaryEditor
- ์ค๋์ ์ผ๊ธฐ์ ์ผ๊ธฐ ๋ด์ฉ์ ์์ฑํ๋ฉด onChange๊ฐ ๋ฐ์ํ๋ ๋์, ์ค๋์ ๊ฐ์ ์ ๊ฐ์ ํญ๋ชฉ๋ค์ด ๋ฆฌ๋ ๋๋ง์ด ์ผ์ด๋จ
- DiaryEditor ์ปดํฌ๋ํธ์์ ์ผ๊ธฐ ๋ด์ฉ์ด ๋ณํ๋ฉด ์์ ์ปดํฌ๋ํธ์ธ EmotionItem ์ปดํฌ๋ํธ์์๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์
- ๋ฐ๋ผ์ EmotionItem ์ปดํฌ๋ํธ์ React.memo() ์ฌ์ฉ```js
// src/components/EmotionItem.jsimport React from "react";
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_descript,
onClick,
isSelected
}) => {
...
};...
export default React.memo(EmotionItem);
```- ํ์ง๋ง, ๊ณ์ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋๋ฐ ์ด ์ด์ ๋ EmotionItem์ด Props๋ก onClick์ ์ ๋ฌ๋ฐ๋๋ฐ ํด๋น onClick์ผ๋ก ์ ๋ฌ๋ฐ๋ ํจ์๊ฐ useState์ ์ํ ๋ณํ ํจ์, useCallback์ผ๋ก ๋ฌถ์ ํจ์๊ฐ ์๋ ๊ฒฝ์ฐ, ์ปดํฌ๋ํธ ์์ฑ ์, ๋ค์ ์์ฑ๋๋ฏ๋ก React.memo()๋ฅผ ์ ์ฉํ ๊ฐํ๋ ์ปดํฌ๋ํธ์ผ์ง๋ผ๋ ๋ฆฌ๋ ๋๋ง์ ๋ฐ์์ํด
- ๋ฐ๋ผ์ onClick์ ์ ๋ฌํ๋ ํจ์๋ฅผ useCallback()์ผ๋ก ๋ฌถ์ด์ฃผ๊ธฐ```js
// src/components/DiaryEditor.jsimport {useCallback, useContext, useEffect, useRef, useState} from "react";
...
const handleClickEmote = useCallback((emotion) => {
setEmotion(emotion);
}, []);
...
์ค๋์ ๊ฐ์
{emotionList.map((it) => (
))}
...
```
## 10. ๋ฐฐํฌ ์ค๋น & ํ๋ก์ ํธ ๋น๋
### 10-1. ๊ธฐ๋ณธ title ํ๊ทธ ์์
- ๋ธ๋ผ์ฐ์ ์ ํญ์ ๋ณด๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก React App์ผ๋ก ํ์๋จ
- ํด๋น ๋ด์ฉ์ head ํ๊ทธ ์์ title ํ๊ทธ์ ๋ฌธ์๊ฐ ์ถ๋ ฅ๋๋ ๊ฒ์ผ๋ก ํ์ด์ง์ ๋ง๊ฒ ์ถ๋ ฅ๋ ํ์๊ฐ ์์```html
...
๊ฐ์ ์ผ๊ธฐ์ฅ```
### 10-2. description ์์
- ์ฑ, ์๋น์ค์ ์ค๋ช ์ ๋ํ๋ด๋ description meta ํ๊ทธ ๋ด์ฉ ์์
```html
...
...```
### 10-3. lang ์์
- ์ธ์ด๋ ํ๊ตญ์ด๋ก ์ค์
```html
...
```
### 10-4. ํ์ด์ง๋ง๋ค title ๋ณํ
- useEffect๋ฅผ ์ฌ์ฉํ์ฌ ํ์ด์ง ๋ง์ดํธ ์, ํ์ด์ง ์ด๋ฆ ๋ณ๊ฒฝ ์งํ
- DOM์ getElementsByTagName ๋ฌธ๋ฒ์ผ๋ก title ํ๊ทธ ๋ชจ๋ ์ ํ
- ๋ฐฐ์ด์ด ๋ฐํ๋๊ธฐ์ ์ฒซ๋ฒ์งธ ์์ ์ ํ
- ํด๋น ์์์ innerHTML์ ๋ณ๊ฒฝํด์ฃผ๊ธฐ
- ๋ค๋ฅธ ํ์ด์ง๋ค๋ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก ์์ ```js
// src/pages/Diary.js...
useEffect(() => {
const titleElement = document.getElementsByTagName("title")[0];
titleElement.innerHTML = `๊ฐ์ ์ผ๊ธฐ์ฅ - ${id}๋ฒ ์ผ๊ธฐ`;
}, []);
...
```
### 10-5. favicon.ico ๋ณ๊ฒฝ
- ํญ์์ ํ์๋๋ ์์ด์ฝ ๋ณ๊ฒฝ
- public ํด๋ ์์ favicon.ico ํ์ผ์ ๋์ผํ ์ด๋ฆ์ ๋ก๊ณ ๋ก ๊ต์ฒดํ๊ธฐ
### 10-6. ๋น๋
- ์์ค์ฝ๋์ ์ค๋ฐ๊ฟ, ๋์์ฐ๊ธฐ ๋ฑ์ ๋ชจ๋ ์ฉ๋์ ์ฐจ์งํ๊ธฐ์ ์ด๋ฅผ ์์ถํ์ฌ ๋ฐฐํฌํ๊ธฐ ์ต์ ์ ์ํ๋ก ๋ง๋ค๊ธฐ
- ์ด๋ฅผ ๋น๋๋ผ๊ณ ํจ
- package.json ํ์ผ์ scripts์ build๋ฅผ ํตํด ์งํํ ์ ์์```bash
$ npm run build
```
### - ๋ก์ปฌ์ ๋ฐฐํฌํด๋ณด๊ธฐ
- serve ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก serve ํจํค์ง ์ค์น
- serve -s build ๋ช ๋ น์ด๋ฅผ ํตํด ๋ก์ปฌ์ ๋ฐฐํฌ
- ๋ก์ปฌ ๋ฐฐํฌ๋ฅผ ํ๋ ์ด์ ๋ ์ค์ ์๋น์ค ๋ฐฐํฌ ์, ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ๋ฅผ ์ฌ์ ์ ํ ์คํธ ํด๋ณด๊ธฐ ์ํจ```bash
$ npm install -g serve$ serve -s build
```
### - ์ผ๊ธฐ ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ
- ์ผ๊ธฐ ๋ฐ์ดํฐ๊ฐ ์๋ ์ํ์์ ์๋ก๊ณ ์นจ ์, ์๋ฌ ๋ฐ์
- App ์ปดํฌ๋ํธ์์ diaryList๊ฐ ๋น๋ฐฐ์ด์ผ ๊ฒฝ์ฐ, truthy๊ฐ ๋๋๋ฐ ์ด๋, ์ฒซ๋ฒ์งธ ์์์ ์ ๊ทผํ๋ ์ฝ๋๊ฐ ์์
- ๋ฐ๋ผ์ ์กฐ๊ฑด๋ฌธ์ ํตํด diaryList์ ๊ธธ์ด๊ฐ 1 ์ด์์ผ ๋, ํด๋น ์์ ์ ์ํํ๋๋ก ์์ ```js
// src/App.js...
useEffect(() => {
const localData = localStorage.getItem("diary");
if (localData) {
const diaryList = JSON.parse(localData).sort((a, b) => parseInt(b.id) - parseInt(a.id));// ์กฐ๊ฑด๋ฌธ ์ถ๊ฐ
if (diaryList.length >= 1) {
dataId.current = parseInt(diaryList[0].id) + 1;
dispatch({type: "INIT", data: diaryList});
}
}
}, []);
...
```
### - title ์๋ฌ
- ์ผ๊ธฐ ์์ฑ ํ, Home์ผ๋ก ๋ฆฌ๋ค์ด๋ ์ ์, ํญ์ title์ด ์ผ๊ธฐ ์์ฑ์ผ๋ก ๋จธ๋ฌผ๋ฌ ์์
- ์ผ๊ธฐ ์ญ์ ํ, Home์ผ๋ก ๋ฆฌ๋ค์ด๋ ์ ์, ํญ์ title์ด ์ผ๊ธฐ ์์ ์ผ๋ก ๋จธ๋ฌผ๋ฌ ์์
- ์ด๋ Home ์ปดํฌ๋ํธ๋ useEffect์ DOM ๋ฌธ๋ฒ์ ์ด์ฉํ์ฌ title์ ๋ฐ๊ฟ์ฃผ๋ ์ฝ๋๋ฅผ ์์ฑํ์ง ์์์ ์ค๋ฅ๊ฐ ๋ฐ์ํจ```js
// src/pages/Home.js...
useEffect(() => {
const titleElement = document.getElementsByTagName("title")[0];
titleElement.innerHTML = `๊ฐ์ ์ผ๊ธฐ์ฅ`;
}, []);
...
```
## 11. Firebase ๋ฐฐํฌ
- ๋ฐฐํฌ๋ ์๋ฒ ์ ์ง ๋น์ฉ, ํด๋ผ์ฐ๋ ๋์ฌ ๋น์ฉ, ๋ฐฉํ๋ฒฝ ๋ฑ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ณ ๋ คํด์ผ ํ ์์๊ฐ ๋ง์
- Firebase๋ ๊ตฌ๊ธ์์ ์ ๊ณตํ๋ฉฐ ์ฝ๊ฒ ๋ฐฐํฌํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ฐฐํฌ ์๋ฃจ์ ์
- [Firebase ๊ณต์ ์ฌ์ดํธ](https://firebase.google.com/?hl=ko)
### 11-1. Firebase ์ฌ์ฉํด๋ณด๊ธฐ
### - ํํ์ด์ง
![ํํ์ด์ง](README_img/firebase_home.png)
- ์์ํ๊ธฐ ํด๋ฆญ
### - ์ฝ์ ํ์ด์ง
![์ฝ์ ํ์ด์ง](README_img/firebase_console.png)
- ํ๋ก์ ํธ ๋ง๋ค๊ธฐ ํด๋ฆญ
### - ํ๋ก์ ํธ ๋ง๋ค๊ธฐ 3๋จ๊ณ
![ํ๋ก์ ํธ ์ด๋ฆ](README_img/firebase_name.png)
- ํ๋ก์ ํธ ์ด๋ฆ ์ง์ ํ๊ธฐ
- ํด๋น ์ด๋ฆ์ ์ฌ๋ฌ ์ฌ์ดํธ๋ฅผ ํฌ๊ด ํ ์ ์์ผ๋ฉฐ, ๋๋ฉ์ธ ์ด๋ฆ์ด๋ ๋ณ๊ฐ์
![๊ตฌ๊ธ ๋ถ์](README_img/firebase_analytics.png)
- ํธ๋ํฝ ๋ถ์ ๋ฑ ๊ตฌ๊ธ์ ์ ๋ณด ์ ๊ณต ์ ๋ฌด ๊ฒฐ์
![ํ๋ก์ ํธ ์ค๋น ์๋ฃ](README_img/firebase_prepare_done.png)
- ํ๋ก์ ํธ ์์ฑ๋จ
### - ํธ์คํ
![์ฝ์ ํ์ด์ง](README_img/firebase_console_re.png)
- ํ๋ก์ ํธ ์ฝ์ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ์ ๋จ
![ํธ์คํ ๋ฉ๋ด](README_img/firebase_hosting.png)
- ๋น๋ ๋ฉ๋ด์ ํธ์คํ ํด๋ฆญ
![ํธ์คํ ํ์ด์ง](README_img/firebase_hosting_page.png)
- ํธ์คํ ํ์ด์ง๋ก ์ด๋
- ํธ์คํ ์์ํ๊ธฐ ํด๋ฆญ
![firebase cli](README_img/firebase_cli.png)
- Firebase CLI ์ค์นํ๊ธฐ
![firebase login init](README_img/firebase_login_init.png)
- ๋ฃจํธ ๋๋ ํ ๋ฆฌ์์ firebase login ๋ช ๋ น์ด ์ํ
![ํ์ฉ](README_img/firebase_allow.png)
- ์ค๋ฅ ์์ง ์ฌ๋ถ ๊ฒฐ์
![๋ก๊ทธ์ธ ์๋ฃ](README_img/firebase_login_done.png)
- ๋ก๊ทธ์ธ ์๋ฃ
### - ๋ฐฐํฌ ์ธํ
![ํธ์คํ ์ผ๋ก ์ธํ ](README_img/firebase_setting1.png)
- ํธ์คํ ์ผ๋ก ๋ฐฐํฌ ์งํ
![์ต์ ](README_img/firebase_setting2.png)
- ํ์ฌ ๋ง๋ค์ด์ง ํ๋ก์ ํธ, ์ ํ๋ก์ ํธ ๋ฑ ์ต์ ์ ํ
![ํ๋ก์ ํธ ์ ํ](README_img/firebase_setting3.png)
- ํด๋น ๋๋ ํ ๋ฆฌ๋ฅผ ๋ฐฐํฌํ Firebase ํ๋ก์ ํธ ์ ํ
![์ฌ๋ฌ ์ธํ ์ ํ](README_img/firebase_setting4.png)
- ํธ์คํ ์ ๊ด๋ จ๋ ์ฌ๋ฌ ์ธํ ์ค์ ํ๊ธฐ
![firebase ํ์ผ ์์ฑ](README_img/firebase_new_files.png)
- ์ธํ ์ ์๋ฃ ์, `.firebaserc`์ `firebase.json` ํ์ผ์ด ์์ฑ๋จ
![๋ฐฐํฌ](README_img/firebase_deploy.png)
- ๋ฐ๋ก ๋ฐฐํฌํ ์ ์์ง๋ง ์ฝ์๋ก ์ด๋
![๋๋ฉ์ธ ์ถ๊ฐ](README_img/firebase_add_new_site.png)
- ์ฝ์ ํ์ด์ง ์๋์ ๋ค๋ฅธ ์ฌ์ดํธ ์ถ๊ฐ ํด๋ฆญ
![๋๋ฉ์ธ ์ด๋ฆ ์์ฑ](README_img/firebase_write_domain.png)
- ์ฌ์ฉํ ๋๋ฉ์ธ ์ด๋ฆ ์์ฑ
![๋๋ฉ์ธ ์ด๋ฆ ํ์ผ์ ์์ฑ](README_img/firebase_add_domain.png)
- firebase.json ํ์ผ์์ hosting ์ต์ ์ `"site": "์์ฑํ ๋๋ฉ์ธ"` ์ถ๊ฐํ๊ธฐ
```bash
$ npm run build
```- ๋น๋ ๋ค์ ์งํ
```bash
$ firebase deploy
```- ๋ฐฐํฌ ์งํ
![์๋ฃ](README_img/firebase_done.png)
- ๋ฐฐํฌ ์๋ฃ๋จ
## 12. Open Graph
- Open Graph์ ์ ๋ณด๋ ํด๋น ๋งํฌ๋ฅผ ๊ณต์ ํ ๋, `์ธ๋ค์ผ, ์ฌ์ดํธ ์ด๋ฆ, ๊ฐ๋จํ ์ค๋ช `์ ์ ๊ณตํ๊ธฐ ์ํด ์์ฑํจ
- `property="og:<์ ๊ณตํ ์ ๋ณด>"` ์ `content="์ด๋ฏธ์ง ์ฃผ์ ๋ฐ ๋ด์ฉ"`์ ์์ฑ์ ๊ฐ์ง meta ํ๊ทธ๋ฅผ ์ถ๊ฐํ๊ธฐ```html
...
...```