뭐임 어이없네
Routing
이란?
Router
Route+ing
Page Routing
?
ㄴ 단순히 요청에 따라서 어떤 페이지를 돌려줄지 요청하는 과정을 일컫는 말
이렇게 홈이라는 경로를 가지고 도착한 요청에는 해당 경로에 알맞는 html 파일을 보내주는 형식으로 웹서버가 동작하게 됨
ㄴ 이렇게 여러개의 페이지를 준비 해놨다가 요청이 들어오면 경로에 따라 적절한 페이지를 보내주는 것
= Multipage Application
= MPA
하지만 !! 우리가 배우는 리액트는
MPA가 아니라 Single Page Application (SPA)
방식을 따른다
ㄴ 페이지가 한개 짜리인 간단한 web application이다
(만들어볼 감정일기장 데모사이트)
ㄴ 귀엽다 !!
유데미 사이트는 페이지를 이동할 때마다 탭의 아이콘이 깜빡거리고 실제로 페이지가 이동될 때 깜빡인 다음에 이동이 됨
vs
리액트 웹페이지는 페이지가 이동할 때 전혀 깜빡이지가 않는다 ! 페이지들의 이동이 아주 빠르고 쾌적하다
ㄴ 어떻게 가능한거냐??
페이지 이동하고 싶어서 버튼 클릭하면
리액트가 알아서 페이지 업데이트 시킴 (컴포넌트가 교체될 때 빠른 속도로페이지가 변경된다!) -> 페이지 전환이 빨라진다
이런 방식을 Client Side Rendering (CSR)
이라고 한다
=> 리액트는 SPA 방식을 따르면서 CSR로 페이지를 렌더링한다 !
시작도 못하고 헤매는 중..
눈물날고 같다
왜 npm start가 안되는거지? 내가 볼 땐 일단 react router 까는거에서 뭔가 잘못된 것 같당.. 근데 난 복붙 밖에 안했는데 잘못될 수가 있나? 날 억까하는듯 ㅠ 따흐흑 일단 난 시간이 없으니 계속 간다.. ㅡㅡ;
pages 폴더 만들고 그 안에 diary, edit, home, new 파일 넣는다 그 후에
const Diary=()=>{
return (
<div>
<h1>Diary</h1>
<p>이 곳은 일기 상세 페이지 입니다.</p>
</div>
);
};
export default Diary;
ㄴ 이렇게 코드 쓰고 이걸 복붙해서 나머지 파일들에도 이름만 바꿔서 복붙 !!
import './App.css';
import {BrowserRouter, 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';
function App() {
return (
<BrowserRouter>
<div className="App">
<h2>App.js</h2>
<Routes>
<Route path='/' element={<Home/>}/>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
아까 4개 파일들 import 하고 함수 값 더 넣어준다
-last code-
(App.js)
import './App.css';
import {BrowserRouter, Route, Routes} from 'react-router-dom';
import RouteTest from './components/RouteTest';
import Home from './pages/Home';
import New from './pages/New';
import Edit from './pages/Edit';
import Diary from './pages/Diary';
function App() {
return (
<BrowserRouter>
<div className="App">
<h2>App.js</h2>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary' element={<Diary/>}/>
</Routes>
<RouteTest/>
</div>
</BrowserRouter>
);
}
export default App;
리액트 앱이 제공하는 index.html은 하나지만 app component와 그 안에 router들을 통해서 마치 페이지가 이동된 것 처럼 만들어준다
=> 리액트는 이런 방식으로 페이지를 전환해줌 (쾌적 & 빠름) = CSR
이 세가지 방법을 배워 보즈아
경로에 변수를 사용하는 방법 -> path variable
(얘네는 useParams라는 토글을 사용해서 가져옴)
<Route path='/diary/:id' element={<Diary/>}/>
-> 뒤에 있는 id라는 이름으로 뒤에 있는 값을 전달하겠다 라는 선언
(Diary.js)
import {useParams} from 'react-router-dom';
const Diary=()=>{
const {id}=useParams();
return (
<div>
<h1>Diary</h1>
<p>이 곳은 일기 상세 페이지 입니다.</p>
</div>
);
};
export default Diary;
/edit?id=10&mode=dark (이름과 값을 엮어서 데이터를 전송하는 기법)
=> Query String
query ex)
import {useSearchParams} from 'react-router-dom';
const Edit=()=>{
const [searchParams, setSearchParams]=useSearchParams();
const id = searchParams.get('id');
const mode = searchParams.get('mode');
return (
<div>
<h1>Edit</h1>
<p>이 곳은 일기 수정 페이지 입니다.</p>
<button onClick={()=>setSearchParams({who:'winterlood'})}>
QS 바꾸기
</button>
</div>
);
};
export default Edit;
navigate 함수를 써서 실행함
page moving ex)
import {useSearchParams} from 'react-router-dom';
const Edit=()=>{
const navigate = useNavigate();
const [searchParams, setSearchParams]=useSearchParams();
const id = searchParams.get('id');
const mode = searchParams.get('mode');
return (
<div>
<h1>Edit</h1>
<p>이 곳은 일기 수정 페이지 입니다.</p>
<button onClick {()=>setSearchParams({who:'winterlood'})}>
QS 바꾸기
</button>
<button onClick={()=>{
navigate("/home");
}}>HOME으로 가기</button>
<button onClick={()=>{
navigate(-1);
}}>뒤로 가기</button>
</div>
);
};
export default Edit;
버튼 안에 onclick 속에 navigate로 실행
첫번째는 home으로 가야돼서 navigate("/home");
이렇게 해놨고
두번째는 뒤로가기여서 navigate(-1);
로 함 -1로 뒤로 가기가 실행된다 !!
@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');
.App{
padding:20px;
font-family: 'Yeon Sung', cursive;
font-family: "Nanum Pen Script",cursive;
}
구글 웹 폰트에 들어가서 url을 다운받고
App.css에 복붙해준다
@media (min-width:650px){
.App{
width:640px;
}
}
@media(max-width:650px){
.App{
width: 90vw;
}
}
650px 이상일 때 .App은 넓이를 640px이 되게 하겠다
650px이하일 때 .App 은 90vw (지금 화면에서 90퍼센트를 차지하게 하겠다)
@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');
body{
background-color: #f6f6f6;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Nanum Pen Script';
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;
}
ㄴ 레이아웃 세팅 완료 !!
emotion 파일들을 assets 폴더 생성 후 집어넣어준다 !
그 후에
<img src={process.env.PUBLIC_URL + `/assets/emotion1.png`}/>
App.js에다가 코드를 넣어준다
process.env.PUBLIC_URL
-> public 디렉토리를 경로화 하는것
<img src={process.env.PUBLIC_URL + `/assets/emotion1.png`}/>
<img src={process.env.PUBLIC_URL + `/assets/emotion2.png`}/>
<img src={process.env.PUBLIC_URL + `/assets/emotion3.png`}/>
<img src={process.env.PUBLIC_URL + `/assets/emotion4.png`}/>
<img src={process.env.PUBLIC_URL + `/assets/emotion5.png`}/>
5개 다 깔아준다
const env=process.env;
env.PUBLIC_URL=env.PUBLIC_URL || "";
ㄴenv.PUBLIC이 있다면 담고 아니면 말아라
-last code-
import './App.css';
import {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';
function App() {
const env=process.env;
env.PUBLIC_URL=env.PUBLIC_URL || "";
return (
<BrowserRouter>
<div className="App">
<h2>App.js</h2>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary/:id' element={<Diary/>}/>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
(App.css)
@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');
body{
background-color: #f6f6f6;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Nanum Pen Script';
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;
}
/*MyButton*/
.MyButton{
cursor: pointer;
border: none;
border-radius: 5px;
padding-top: 10px;
padding-bottom: 20px;
padding-right: 20px;
padding-left: 20px;
font-size: 18px;
white-space: nowrap;
font-family: 'Nanum Pen Script';
}
.MyButton_default{
background-color: #ececec;
color: black;
}
.MyButton_positive{
background-color: #64c964;
color: white;
}
.MyButton_negative{
background-color: #fd565f;
color:white;
}
(App.js)
import './App.css';
import {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';
// COMPONENTS
import MyButton from "./components/MyButton";
function App() {
const env=process.env;
env.PUBLIC_URL=env.PUBLIC_URL || "";
return (
<BrowserRouter>
<div className="App">
<h2>App.js</h2>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
type={"positive"}
/>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
type={"negative"}
/>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
/>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary/:id' element={<Diary/>}/>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
(MyButton.js)
const MyButton = (text,type,onClick)=>{
//없는 글자가 나타나면 강제로 default로 바꿔버림
const btnType=['positive','negative'].includes(type)? type:'default';
return(
<button className={["MyButton",`MyButton_${type}`].join(" ")}
onClick={onClick}
>
{text}
</button>
);
};
MyButton.defaultProps={
type:"default",
};
export default MyButton;
헤더 컴포넌트 만들기 !
(App.js)
import './App.css';
import {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';
// COMPONENTS
import MyButton from "./components/MyButton";
function App() {
const env=process.env;
env.PUBLIC_URL=env.PUBLIC_URL || "";
return (
<BrowserRouter>
<div className="App">
<MyHeader
headText={"App"}
leftChild={
<MyButton text={'왼쪽버튼'} onClick={()=> alert("왼쪽 클릭")}/>
}
rightChild={
<MyButton
text={'오른쪽 버튼'}
onClick={()=> alert("오른쪽 클릭")}
/>
}
/>
<h2>App.js</h2>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
type={"positive"}
/>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
type={"negative"}
/>
<MyButton
text={'버튼'}
onClick={()=>alert("버튼 클릭")}
/>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary/:id' element={<Diary/>}/>
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
(App.css)
@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');
body{
background-color: #f6f6f6;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Nanum Pen Script';
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;
}
/*MyButton*/
.MyButton{
cursor: pointer;
border: none;
border-radius: 5px;
padding-top: 10px;
padding-bottom: 20px;
padding-right: 20px;
padding-left: 20px;
font-size: 18px;
white-space: nowrap;
font-family: 'Nanum Pen Script';
}
.MyButton_default{
background-color: #ececec;
color: black;
}
.MyButton_positive{
background-color: #64c964;
color: white;
}
.MyButton_negative{
background-color: #fd565f;
color:white;
}
/*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 Pen Script";
}
(MyHeader.js)
const MyHeader =({headText,leftChild,rightChild})=>{
return <header>
<div className="head_btn_left">
{leftChild}
</div>
<div className="head_text">
{headText}
</div>
<div className="head_btn_right">
{rightChild}
</div>
</header>
}
export default MyHeader;
(App.js)
import React, { useReducer, useRef } from 'react';
import './App.css';
import {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';
import { useReducer } from 'react';
const reducer=(state,action)=>{
let newState=[];
switch(action.type){
case 'INIT':{
return action.data;
}
case 'CREATE':{
newState=[action.data,...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;
};
export const DiaryStateContext=React.createContext();
export const DiaryDispatchContext = React.createContext();
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,
},
});
};
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider
value={{
onCreate,
onEdit,
onRemove,
}}
>
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary/:id' element={<Diary/>}/>
</Routes>
</div>
</BrowserRouter>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
}
export default App;
오늘 만들 결과물 !!
(App.js)
import React, { useReducer, useRef } from 'react';
import './App.css';
import {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';
import { useReducer } from 'react';
const reducer=(state,action)=>{
let newState=[];
switch(action.type){
case 'INIT':{
return action.data;
}
case 'CREATE':{
newState=[action.data,...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;
};
export const DiaryStateContext=React.createContext();
export const DiaryDispatchContext = React.createContext();
const dummyData=[
{
id:1,
emotion:1,
content:"오늘의 일기 1번",
date:12321
},
{
id:2,
emotion:2,
content:"오늘의 일기 2번",
date:12322
},
{
id:3,
emotion:3,
content:"오늘의 일기 3번",
date:12323,
},
{
id:4,
emotion:4,
content:"오늘의 일기 4번",
date:12324,
},
{
id:5,
emotion:5,
content:"오늘의 일기 5번",
date:12325,
},
];
function App() {
const [data,dispatch]=useReducer(reducer,dummyData);
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,
},
});
};
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider
value={{
onCreate,
onEdit,
onRemove,
}}
>
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Home/>}/>
<Route path='/new' element={<New/>}/>
<Route path='/edit' element={<Edit/>}/>
<Route path='/diary/:id' element={<Diary/>}/>
</Routes>
</div>
</BrowserRouter>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
}
export default App;
(App.css)
@import url('https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap');
body{
background-color: #f6f6f6;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Nanum Pen Script';
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;
}
/*MyButton*/
.MyButton{
cursor: pointer;
border: none;
border-radius: 5px;
padding-top: 10px;
padding-bottom: 20px;
padding-right: 20px;
padding-left: 20px;
font-size: 18px;
white-space: nowrap;
font-family: 'Nanum Pen Script';
}
.MyButton_default{
background-color: #ececec;
color: black;
}
.MyButton_positive{
background-color: #64c964;
color: white;
}
.MyButton_negative{
background-color: #fd565f;
color:white;
}
/*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 Pen Script";
}
/*DiaryList*/
.DiaryList.menu_wrapper{
margin-top: 20px;
margin-bottom: 30px;
display: flex;
justify-content: space-between;
}
.DiaryList.menu_wrapper .right_col{
flex-grow: 1;
}
.DiaryList.menu_wrapper .right_col button{
width: 100%;
}
.DiaryList .ControlMenu{
margin-right: 10px;
border: none;
border-radius: 5px;
background-color: #ececec;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
cursor: pointer;
font-family: 'Nanum Pen Script';
font-size: 18px;
}
/*Diary Item*/
.DiaryItem{
padding-top: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #e2e2e2;
display: flex;
justify-content: space-between;
}
.DiaryItem .emotino_img_wrapper{
cursor: pointer;
min-width: 120px;
height: 80px;
border-radius: 5px;
display: flex;
justify-self: center;
}
.DiaryItem .emotino_img_wrapper_1{
background-color: #64c964;
}
.DiaryItem .emotino_img_wrapper_2{
background-color: #9dd772;
}
.DiaryItem .emotino_img_wrapper_3{
background-color: #fdce17;
}
.DiaryItem .emotino_img_wrapper_4{
background-color: #fd8446;
}
.DiaryItem .emotino_img_wrapper_5{
background-color: #fd565f;
}
.DiaryItem .emotino_img_wrapper img{
width: 50%;
}
.DiaryItem .info_wrapper{
flex-grow: 1;
margin-left: 20px;
cursor: pointer;
}
.DiaryItem .diarydate{
font-weight: bold;
font-size: 25px;
margin-bottom: 5px;
}
.DiaryItem .diary_content_preview{
font-size: 18px;
}
.DiaryItem .btn_wrapper{
min-width: 70px;
}
(Home.js)
import { useContext, useEffect, useState } from "react";
import { DiaryStateContext } from "../App";
import MyHeader from'./../components/MyHeader';
import MyButton from './../components/MyButton';
import DiaryList from "../components/DiaryList";
const Home=()=>{
const diaryList=useContext(DiaryStateContext);
const [data,setData]=useState([]);
const [curDate,setCurDate]=useState(new Date());
const headText =`${curDate.getFullYear()}년 ${curDate.getMonth()+1 }월`
useEffect(()=>{
if(diaryList.length>=1){
const firstDay =new Date(
curDate.getFullYear(),
curDate.getMonth(),
1
).getTime();
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth()+1,
0
).getTime();
setData(
diaryList.filter((it)=>firstDay<=it.date && it.data<=lastDay)
);
}
},[diaryList, curDate]);
useEffect(()=>{
console.log(data);
},[data]);
const increaseMonth=()=>{
setCurDate(
new Date(curDate.getFullYear(),curDate.getMonth()+1,curDate.getDate())
);
};
const decreaseMonth=()=>{
setCurDate(
new Date(curDate.getFullYear(),curDate.getMonth()-1,curDate.getDate())
);
};
return (
<div>
<MyHeader headText={headText}
leftChild={<MyButton text={"<"} onClick={()=>{}} />}
rightChild={<MyButton text={">"} onClick={increaseMonth}/>}
/>
<DiaryList diaryList={data}/>
</div>
);
};
export default Home;
(DiaryList.js)
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
import DiaryItem from "./DiaryItem";
const sortOptionList=[
{value:"latest",name:"최신순"},
{value:"oldest",name:"오래된 순"},
];
const filterOptionList = [
{value:"all",name:"전부 다"},
{value:"good", name:"좋은 감정만",},
{value:"bad", name:"안좋은 감정만",},
];
const ControlMenu=({value,onChange,optionList})=>{
return (
<select
className="ControlMenu"
value={value}
onChange={(e)=>onChange(e.target.value)}
>
{optionList.map((it,idx)=>(
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
};
const DiaryList = ({diaryList})=>{
const navigate = useNavigate();
const [sortType,setSortType]=useState('latest');
const [filter,setFilter]=useState("all");
const getProcessedDiaryList=()=>{
const filterCallBack=(item)=>{
if(filter==='good'){
return parseInt(item.emotion)<=3;
}else{
return parseInt(item.emotion)>3;
}
}
const compare =(a,b)=>{
if(sortType==='latest'){
return parseInt(b.date)-parseInt(a.date);
}
else{
return parseInt(a.date)-parseInt(b.date);
}
};
const copyList = JSON.parse(JSON.stringify(diaryList)) //diaryList를 문자열로 바꿈
const filteredList =
filter === 'all'? copyList:copyList.filter((it)=>filterCallBack(it));
const sortedList = copyList.sort(compare);
return sortedList;
};
return (
<div className="DiaryList">
<div className="menu_wrapper">
<div className="left_col">
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
</div>
<div className="right_col">
<MyButton
type={'positive'}
text={'새 일기 쓰기'}
onClick={()=>navigate('/new')}
/>
</div>
</div>
{getProcessedDiaryList.map((it)=>(
<DiaryItem key={it.id} {...it}/>
))}
</div>
);
};
DiaryList.defaultProps={
diaryList:[],
};
export default DiaryList;
(DiaryItem.js)
import MyButton from "./MyButton";
import {useNavigate} from "react-router-dom";
const DiaryItem = ({id,emotion,content,date})=>{
const env=process.env;
env.PUBLIC_URL=env.PUBLIC_URL||"";
const strDate = new Date(parseInt(date)).toLocaleDateString();
const goDetail = ()=>{
navigate(`/diary/${id}`);
};
const goEdit=()=>{
navigate(`/edit/${id}`);
};
return (
<div className="DiaryItem">
<div
className={["emotion_img_wrapper",
`emotion_img_wrapper_${emotion}`,
].join(" ")}
>
<img src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`}/>
</div>
<div onClick={goDetail} className="info_wrapper">
<div className="diary_date">{strDate}</div>
<div className="diary_content_preview">{content.slice(0,25)}</div>
</div>
<div className="btn_wrapper">
<MyButton onClick={goEdit} text={"수정하기"}/>
</div>
</div>
);
};
export default DiaryItem;
끝 !!
하지만 아직도 npm start는 되지 않고 있다... 슾ㄹ프다 ㅜㅡㅜ
아무래도 파일을 삭제하고 다시 해야 할듯 ..
저번에 안되던 npm start를 해결하기 위해 원래 썼던 emotionDiary를 지우고 emotionDairy2를 만들어서 에러를 다 고쳐보았당
근데 문제가 또 발생
아무것도 안 나옴.. 진짜 억까다 이건
미치겠음
New.js 생성
import DiaryEditor from "../components/DiaryEditor";
const New=()=>{
return(
<div>
<DiaryEditor/>
</div>
);
};
export default New;
DiaryEditor.js에다가
const emotionList = [
{
emotion_id:1,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion1.png`,
emotion_descript : '완전 좋음'
},
{
emotion_id:2,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion2.png`,
emotion_descript : '좋음'
},
{
emotion_id:3,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion3.png`,
emotion_descript : '그럭저럭'
},
{
emotion_id:4,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion4.png`,
emotion_descript : '나쁨'
},
{
emotion_id:5,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion5.png`,
emotion_descript : '끔찍함'
},
]
감정 객체 넣고
DiaryEditor.js full code
import { useNavigate } from "react-router-dom";
import { useContext, useRef, useState } from "react";
import MyHeader from "./MyHeader";
import MyButton from "./MyButton";
import EmotionItem from "./EmotionItem";
import { DiaryDispatchContext } from "../App";
const emotionList = [
{
emotion_id:1,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion1.png`,
emotion_descript : '완전 좋음'
},
{
emotion_id:2,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion2.png`,
emotion_descript : '좋음'
},
{
emotion_id:3,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion3.png`,
emotion_descript : '그럭저럭'
},
{
emotion_id:4,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion4.png`,
emotion_descript : '나쁨'
},
{
emotion_id:5,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion5.png`,
emotion_descript : '끔찍함'
},
]
const getStringDate = (date)=>{
return date.toISOString().slice(0,10);
};
const DiaryEditor =()=>{
const contentRef = useRef();
const [content,setContent]=useState("");
const [emotion,setEmotion]=useState(3);
const [date,setDate] = useState(getStringDate(new Date()));
const {onCreate} = useContext(DiaryDispatchContext);
const handleClickEmote = (emotion)=>{
setEmotion(emotion);
};
const navigate = useNavigate();
const handleSubmit=()=>{
if(content.length<1){
contentRef.current.focus();
return;
}
onCreate(date,content,emotion);
navigate('/',{replace:true});
};
return(
<div className="DiaryEditor">
<MyHeader
headText={"새 일기 쓰기"}
leftChild={
<MyButton text ={"< 뒤로가기"} onclick ={()=>navigate(-1)}/>
}
/>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input_box">
<input
className="input_date"
value = {date}
onChange={(e)=>setDate(e.target.value)}
type="date"
/>
</div>
</section>
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it)=>(
<EmotionItem
key = {it.emotion_id}
{...it}
onClick={handleClickEmote}
isSelected = {it.emotion_id===emotion}
/>
))}
</div>
</section>
<section>
<h4>오늘의 일기</h4>
<div className="input_box text_wrapper">
<textarea
placeholder="오늘은 어땠나요 ?"
ref = {contentRef}
value = {content}
onChange={(e)=>setContent(e.target.value)}
/>
</div>
</section>
<section>
<div className="control_box">
<MyButton text = {'취소하기'} onClick={()=>navigate(-1)}/>
<MyButton
text = {"작성완료"}
type={"positive"}
onClick={handleSubmit}
/>
</div>
</section>
</div>
</div>
);
};
export default DiaryEditor;
전체적으로 추가ㅏ해주었따
App.css에 DiaryItem이랑 EmotionItem 속성들 추가해줌
/*DiaryEditor*/
.DiaryEditor section{
margin-bottom: 40px;
}
.DiaryEditor h4{
font-size: 22px;
font-weight: bold;
}
.DiaryEditor .input_date{
border: none;
border-radius: 5px;
background-color: #ececec;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
cursor:pointer;
font-family: "Nanum Pen Script";
font-size: 20px;
}
.DiaryEditor .emotion_list_wrapper{
display: grid;
grid-template-columns: repeat(5,auto);
gap:2%;
}
.DiaryEditor textarea{
font-family: "Nanum Pen Script";
font-size: 20px;
box-sizing: border-box;
width: 100%;
min-height: 200px;
resize: vertical;
border: none;
border-radius: 5px;
background-color: #ececec;
padding: 20px;
}
.DiaryEditor .control_box{
display: flex;
justify-content: space-between;
align-items: center;
}
/*EMOTION ITEM*/
.EmotionItem{
cursor: pointer;
border-radius: 5px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.EmotionItem img{
width: 50%;
margin-bottom: 10px;
}
.EmotionItem span{
font-size: 18px;
}
.EmotionItem_off{
background-color: #ececec;
}
.EmotionItem_on_1{
background-color: #64c964;
color: white;
}
.EmotionItem_on_2{
background-color: #9dd772;
color: white;
}
.EmotionItem_on_3{
background-color: #fdce17;
color: white;
}
.EmotionItem_on_4{
background-color: #fd8446;
color: white;
}
.EmotionItem_on_5{
background-color: #fd565f;
color: white;
}
우선 Edit.js
에다가
import { useContext, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { DiaryStateContext } from '../App';
import DiaryEditor from '../components/DiaryEditor';
const Edit=()=>{
const [originData,setOriginData] = useState();
const navigate = useNavigate();
const {id}=useParams();
const diaryList = useContext(DiaryStateContext);
useEffect(()=>{
if(diaryList.length>=1){
const targetDiary = diaryList.find(
(it)=>parseInt(it.id)===parseInt(id)
);
if(targetDiary){
setOriginData(targetDiary);
} else{
navigate('/',{replace:true});
}
}
},[id,diaryList]);
return (
<div>
{originData&&<DiaryEditor isEdit={true} originData={originData}/>}
</div>
);
};
export default Edit;
이렇게 추가해주었구
DiaryEditor.js
에도 추가해줌
const handleSubmit=()=>{
if(content.length<1){
contentRef.current.focus();
return;
}
if(window.confirm(isEdit?"일기를 수정하시겠습니까?":"새로운 일기를 작성하시겠습니까?")){
if(!isEdit){
onCreate(date,content,emotion)
}else{
OnEdit(originData.id,date,content,emotion);
}
}
navigate('/',{replace:true});
};
ㄴ 함수 추가하기
DiaryEditor.js
full code
import { useNavigate } from "react-router-dom";
import { useContext, useEffect, useRef, useState } from "react";
import MyHeader from "./MyHeader";
import MyButton from "./MyButton";
import EmotionItem from "./EmotionItem";
import { DiaryDispatchContext } from "../App";
const emotionList = [
{
emotion_id:1,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion1.png`,
emotion_descript : '완전 좋음'
},
{
emotion_id:2,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion2.png`,
emotion_descript : '좋음'
},
{
emotion_id:3,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion3.png`,
emotion_descript : '그럭저럭'
},
{
emotion_id:4,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion4.png`,
emotion_descript : '나쁨'
},
{
emotion_id:5,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion5.png`,
emotion_descript : '끔찍함'
},
]
const getStringDate = (date)=>{
return date.toISOString().slice(0,10);
};
const DiaryEditor =(isEdit,originData)=>{
const contentRef = useRef();
const [content,setContent]=useState("");
const [emotion,setEmotion]=useState(3);
const [date,setDate] = useState(getStringDate(new Date()));
const {onCreate,OnEdit} = useContext(DiaryDispatchContext);
const handleClickEmote = (emotion)=>{
setEmotion(emotion);
};
const navigate = useNavigate();
const handleSubmit=()=>{
if(content.length<1){
contentRef.current.focus();
return;
}
if(window.confirm(isEdit?"일기를 수정하시겠습니까?":"새로운 일기를 작성하시겠습니까?")){
if(!isEdit){
onCreate(date,content,emotion)
}else{
OnEdit(originData.id,date,content,emotion);
}
}
navigate('/',{replace:true});
};
useEffect(()=>{
if(isEdit){
setDate(getStringDate(new Date(parseInt(originData.date))));
setEmotion(originData.emotion);
setContent(originData.content);
}
},[isEdit,originData])
return(
<div className="DiaryEditor">
<MyHeader
headText={isEdit ? "일기 수정하기" : "새 일기 쓰기"}
leftChild={
<MyButton text ={"< 뒤로가기"} onclick ={()=>navigate(-1)}/>
}
/>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input_box">
<input
className="input_date"
value = {date}
onChange={(e)=>setDate(e.target.value)}
type="date"
/>
</div>
</section>
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it)=>(
<EmotionItem
key = {it.emotion_id}
{...it}
onClick={handleClickEmote}
isSelected = {it.emotion_id===emotion}
/>
))}
</div>
</section>
<section>
<h4>오늘의 일기</h4>
<div className="input_box text_wrapper">
<textarea
placeholder="오늘은 어땠나요 ?"
ref = {contentRef}
value = {content}
onChange={(e)=>setContent(e.target.value)}
/>
</div>
</section>
<section>
<div className="control_box">
<MyButton text = {'취소하기'} onClick={()=>navigate(-1)}/>
<MyButton
text = {"작성완료"}
type={"positive"}
onClick={handleSubmit}
/>
</div>
</section>
</div>
</div>
);
};
export default DiaryEditor;
Diary.js
를 꾸며보아따
import {useNavigate, useParams} from 'react-router-dom';
import { useContext, useEffect, useState } from 'react';
import { DiaryStateContext } from '../App';
import { getStringDate } from '../util/date';
import MyHeader from '../components/MyHeader';
import MyButton from '../components/MyButton';
import { emotionList } from '../util/emotion';
const Diary=()=>{
const {id}=useParams();
const diaryList = useContext(DiaryStateContext);
const navigate = useNavigate();
const [data,setData]=useState();
useEffect(()=>{
if(diaryList.length>=1){
const targetDiary = diaryList.find(
(it)=>parseInt(it.id)===parseInt(id)
);
if(targetDiary){
//일기가 존재할 때
setData(targetDiary);
}else{
//일기가 없을 때
alert("없는 일기입니다.");
navigate('/',{replace:true});
}
}
},[id,diaryList]);
if(!data){
return <div className='DiaryPage'>로딩중입니다...</div>
}else{
const curEmotionData = emotionList.find(
(it)=>parseInt(it.emotion_id)===parseInt(data.emotion)
);
return (
<div className='DiaryPage'>
<MyHeader headText={`${getStringDate(new Date(data.date))} 기록`}
leftChild={
<MyButton text={"< 뒤로가기"} onClick={()=> navigate(-1)}/>
}
rightChild={
<MyButton text = {"수정하기"}
onClick={()=>navigate(`/edit/${data.id}`)}
/>
}
/>
<article>
<section>
<h4>오늘의 감정</h4>
<div className={[
'diary_img_wrapper',`diary_img_wrapper_${data.emotion}`,
].join(" ")}
>
<img src = {curEmotionData.emotion_img}/>
<div className='emotion_descript'>
{curEmotionData.emotion_descript}
</div>
</div>
</section>
<section>
<h4>오늘의 일기</h4>
<div className='diary_content_wrapper'>
<p>{data.content}</p>
</div>
</section>
</article>
</div>
);
}
};
export default Diary;
그리고 코드가 안 길어지게 util 이라는 폴더를 만들어서 긴 코드를 넣고
import해오는 방법도 새롭게 배움 !
util/date.js
export const getStringDate = (date)=>{
return date.toISOString().slice(0,10);
};
util/emotion.js
export const emotionList = [
{
emotion_id:1,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion1.png`,
emotion_descript : '완전 좋음'
},
{
emotion_id:2,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion2.png`,
emotion_descript : '좋음'
},
{
emotion_id:3,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion3.png`,
emotion_descript : '그럭저럭'
},
{
emotion_id:4,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion4.png`,
emotion_descript : '나쁨'
},
{
emotion_id:5,
emotion_img : process.env.PUBLIC_URL + `/assets/emotion5.png`,
emotion_descript : '끔찍함'
},
]
Diary부분에 해당하는 app.css에 속성도 추가하였음
/*Diary*/
.DiaryPage section{
width: 100%;
margin-bottom: 100px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.DiaryPage h4{
font-size: 22px;
font-weight: bold;
}
.DiaryPage .diary_img_wrapper{
background-color: #ececec;
width: 25px;
height: 25px;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.DiaryPage .diary_img_wrapper_1{
background-color: #64c964;
}
.DiaryPage .diary_img_wrapper_2{
background-color: #9dd772;
}
.DiaryPage .diary_img_wrapper_3{
background-color: #fdce17;
}
.DiaryPage .diary_img_wrapper_4{
background-color: #fd8446;
}
.DiaryPage .diary_img_wrapper_5{
background-color: #fd565f;
}
.DiaryPage .emotion_descript{
font-size: 25px;
color: white;
}
.DiaryPage .diary_content_wrapper{
width: 100%;
background-color: #ececec;
border-radius: 5px;
word-break: keep-all;
overflow-wrap: break-word;
}
.DiaryPage .diary_content_wrapper p{
padding: 20px;
text-align: left;
font-size: 20px;
font-family: 'Yeon Sung';
font-weight: 400;
line-height: 2.5;
}
1) encountered two children 이라는 버그를 만나면 !
const dataId=useRef(0);
-> const dataId=useRef(6);
겹치는 키가 발생을 했는지 확인하고 있다면 바꾸자
2) 오타 !
강의에선 const [sortType,setSortType]=useState('latest');
이거를
const [sortType,setSortType]=useState('lasest');
라고 해놔서 버그가 났당
3) 마지막 시분초 적기 !
원래는
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth()+1,
0,
).getTime();
0 뿐이었지만
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth()+1,
0,
23,
59,
59
).getTime();
이렇게 적어줘야함
state가 갖는 값은 유지되기 힘들다 -> 데이터베이스로 값을 저장해서 새로고침했을 땐 데이터베이스에서 값을 불러오기
localstorage 쓰기
useEffect(()=>{
const item1 = localStorage.getItem('item1');
const item2 = localStorage.getItem('item2');
const item3 = JSON.parse(localStorage.getItem('item3'));
console.log({item1,item2,item3});
},[]);
app.js에다가
useEffect(()=>{
const localData = localStorage.getItem('diary');
if(localData){
const diaryList = JSON.parse(localData).sor;test((a,b)=>parseInt(b.id)-parseInt(a.id));
dataId.current = parseInt(diaryList[0].id)+1
dispatch({type:"INIT",data:diaryList});
}
},[]);
이렇게 localstorage 이용하는 함수 짜줌
DiaryEditor.js에다가 삭제하기 버튼도 만들어따
rightChild={
isEdit&&(
<MyButton
text={"삭제하기"}
type={"negative"}
onClick ={handleRemove}
/>
)
}
1) 코드를 하나하나 보면서 최적화 판단하기 (정적분석)
2) 프로젝트 켜서 도구의 힘을 받아서 찾아내는 방법 (동적분석)
const ControlMenu=React.memo(({value,onChange,optionList})=>{
return (
<select
className="ControlMenu"
value={value}
onChange={(e)=>onChange(e.target.value)}
>
{optionList.map((it,idx)=>(
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
React.memo
로 감싸면 강화된 컴포넌트로 돌려주는 고차컴포넌트임
굳이 쓸모 없는 이런 함수들은 지워준다 !
useEffect(()=>{
console.log("control menu");
});
그리고
const [sortType, setSortType]=useState("latest")
를 쓰면
굳이 아래에다가
const handleSetSortType = (sortType)=>{
setSortType(sortType);
}
를 안써도 된다
이미지, 동영상 등을 렌더링하면 렉걸릴수도 있다 그래서 react.memo적용하기
그리고
DiaryEditor.js 에서
const handleClickEmote = useCallback((emotion)=>{
setEmotion(emotion);
},[]);
수정을 해주었당
package.json에 가면
"build": "react-scripts build"
이렇게 빌드 스크립트가 있당
이제 빌드 하려고 명령어 치려고 했는데
이게 뭐세요...
얘가 나타나기 시작한건 정확히
useEffect(()=>{
const titleElement = document.getElementsByTagName('title')[0];
titleElement.innerHTML = `감정 일기장 - ${id}번 일기 수정`;
},[]);
얘를 입력하기 시작한 후인데
아무리봐도 강의랑 나랑 코드가 똑!!!!같다..
캐열심히 들었는데 여기서 막혀서 빌드 못하면
너무 억울한디...
어이쿠... "npm start"를 못했으면 언제 어디서 오류가 났는지도 모르겠네여.. 다음주까지 새로 폴더 만들어서 할 수 있는 거죠..? 시작부터 안되는 거면 흠 "npx create-react-app emotionDiary"로 폴더를 맞게 만든 건지 확인해바여!