일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- app router
- RTK Query
- recoil
- 투포인터
- Cypress
- Jest
- ESlint
- TS
- 인터섹션
- 타입 좁히기
- 무한 스크롤
- CORS
- 이분 검색
- 리터럴 타입
- 공변성
- 태그된 유니온
- useAppDispatch
- 결정 알고리즘
- webpack
- CI/CD
- tailwind
- React
- dfs
- Promise
- map
- 호이스팅
- 반공변성
- async/await
- autosize
- SSR
- Today
- Total
짧은코딩
HOME 구현하기 본문
헤더
-Home.js
import { useState } from "react";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/MyButton";
const Home = () => {
const [curDate, setCurDate] = useState(new Date());
// month는 1월이 0월로 표시되기 때뮨에 +1 해줘야 한다.
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
// 오른쪽 버튼 누르면 1달씩 증가
const increaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
);
};
// 왼쪽 버튼 누르면 1달씩 감소
const decreaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
);
};
return (
<div>
<MyHeader
headText={headText}
leftchild={<MyButton text={"<"} onClick={decreaseMonth} />}
rightchild={<MyButton text={">"} onClick={increaseMonth} />}
/>
</div>
);
};
export default Home;
이렇게 헤더가 완성되고 왼쪽 버튼은 저번 달, 오른쪽 버튼은 다음 달로 바뀐다.
리스트
지금 당장 데이터가 없기 때문에 App.js에서 dummy 데이터를 임시로 만들어서 리스트를 만든다.
-App.js
const dummyData = [
{
id: 1,
emotion: 1,
content: "오늘의 일기 1번",
date: 1655785838196,
},
{
id: 2,
emotion: 2,
content: "오늘의 일기 2번",
date: 1655785838197,
},
{
id: 3,
emotion: 3,
content: "오늘의 일기 3번",
date: 1655785838198,
},
{
id: 4,
emotion: 4,
content: "오늘의 일기 4번",
date: 1655785838199,
},
{
id: 5,
emotion: 5,
content: "오늘의 일기 5번",
date: 1655785838200,
},
];
function App() {
const [data, dispatch] = useReducer(reducer, dummyData);
이렇게 dummyData 데이터를 만들고 기초 state에 dummyData를 넣었다.
-Home.js
useEffect(() => {
// diary가 비어있으면 밑에 코드 수행할 이유가 없다.
if (diaryList.length >= 1) {
// 그 달의 1일 시간 구하기
const firstDay = new Date(
curDate.getFullYear(),
curDate.getMonth(),
1
).getTime();
// 그 달의 마지막 날 시간 구하기
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth() + 1,
0
).getTime();
// firstDay보다는 커야하고 lastDay보다는 작아야 그 달 안에 있다.
setData(
diaryList.filter((it) => firstDay <= it.date && it.date <= lastDay)
);
}
// diaryList가 바꼈다는 것은 일기가 추가, 수정, 삭제된 것을 의미한다.
// 따라서 diaryList도 넣어줘야 한다.
}, [diaryList, curDate]);
// 제대로 바뀌는지 확인 출력
useEffect(() => {
console.log(data);
}, [data]);
useContect로 데이터를 받아오고 useEffect로 그 달에 맞는 데이터를 추출했다.
-List.js
List는 Home.js가 아니고 따로 만들어 준다.
import { useContext, useState, useEffect } 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);
// 달에 따라 보여주는 리스트를 다르게 하기 위한 state
const [data, setData] = useState([]);
const [curDate, setCurDate] = useState(new Date());
// month는 1월이 0월로 표시되기 때뮨에 +1 해줘야 한다.
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
// useEffect로 curDate가 변화하는 순간 그 달의 데이터를 뽑아와야 한다.
useEffect(() => {
// diary가 비어있으면 밑에 코드 수행할 이유가 없다.
if (diaryList.length >= 1) {
// 그 달의 1일 시간 구하기
const firstDay = new Date(
curDate.getFullYear(),
curDate.getMonth(),
1
).getTime();
// 그 달의 마지막 날 시간 구하기
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth() + 1,
0
).getTime();
// firstDay보다는 커야하고 lastDay보다는 작아야 그 달 안에 있다.
setData(
diaryList.filter((it) => firstDay <= it.date && it.date <= lastDay)
);
}
// diaryList가 바꼈다는 것은 일기가 추가, 수정, 삭제된 것을 의미한다.
// 따라서 diaryList도 넣어줘야 한다.
}, [diaryList, curDate]);
// 제대로 바뀌는지 확인 출력
useEffect(() => {
console.log(data);
}, [data]);
// 오른쪽 버튼 누르면 1달씩 증가
const increaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
);
};
// 왼쪽 버튼 누르면 1달씩 감소
const decreaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
);
};
return (
<div>
<MyHeader
headText={headText}
leftchild={<MyButton text={"<"} onClick={decreaseMonth} />}
rightchild={<MyButton text={">"} onClick={increaseMonth} />}
/>
<DiaryList diaryList={data} />
</div>
);
};
export default Home;
이렇게 내용을 전달 받도록 넣어준다.
-Home.js
다시 Home.js로 돌아와서 import한다.
<div>
<MyHeader
headText={headText}
leftchild={<MyButton text={"<"} onClick={decreaseMonth} />}
rightchild={<MyButton text={">"} onClick={increaseMonth} />}
/>
<DiaryList diaryList={data} />
</div>
그리고 DiaryList를 추가한다.
이렇게 일기가 화면에 출력된다.
필터
-최신순, 오래된 순
import { useState } from "react";
const sortOptionList = [
{ value: "latest", name: "최신순" },
{ value: "oldest", name: "오래된 순" },
];
// 정렬 필터
// value는 select가 어떤걸 선택하고 있는지
// onChange는 select가 변화했을 때, 바꿀 기능을 하는 함수
// optionList는 select 안에 들어갈 옵션이다.
const ControlMenu = ({ value, onChange, optionList }) => {
return (
<select 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 }) => {
// 정렬 기준을 정할 state이고 초기값은 최신으로 할거라서 lastest
const [sortType, setSortType] = useState("lastest");
// 최신순, 오래된 순으로 정렬하는 기능
const getProcesseDiaryList = () => {
const compare = (a, b) => {
if (sortType === "latest") {
return parseInt(b.date) - parseInt(a.date);
} else {
return parseInt(a.date) - parseInt(b.date);
}
};
// stringify(diaryList)를 하면 diaryList가 배열에서 문자열로 바뀐다.
// 그리고 parse를 하면 다시 배열로 바꿔준다.
// diaryList를 안건드리고 copyList에 깊은 복사를 하기 위해서 이렇게 한다.
const copyList = JSON.parse(JSON.stringify(diaryList));
const sortedList = copyList.sort(compare);
return sortedList;
};
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
{getProcesseDiaryList().map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
// 데이터가 정상적으로 전달되지 않으면 빈배열 전달
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
최신순, 오랜된 순으로 정렬을 할 수 있다. 이 때 정렬은 비교하면서 하면된다.
-감정 숫자 기본으로 나오게 하기
import { useState } from "react";
const sortOptionList = [
{ value: "latest", name: "최신순" },
{ value: "oldest", name: "오래된 순" },
];
const filterOptionList = [
{ value: "all", name: "전부다" },
{ value: "good", name: "좋은 감정만" },
{ value: "bad", name: "안좋은 감정만" },
];
// 정렬 필터
// value는 select가 어떤걸 선택하고 있는지
// onChange는 select가 변화했을 때, 바꿀 기능을 하는 함수
// optionList는 select 안에 들어갈 옵션이다.
const ControlMenu = ({ value, onChange, optionList }) => {
return (
<select 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 }) => {
// 정렬 기준을 정할 state이고 초기값은 최신으로 할거라서 lastest
const [sortType, setSortType] = useState("lastest");
// 감정 숫자 기준으로 출력
const [filter, setFilter] = useState("all");
// 최신순, 오래된 순으로 정렬하는 기능
const getProcesseDiaryList = () => {
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);
}
};
// stringify(diaryList)를 하면 diaryList가 배열에서 문자열로 바뀐다.
// 그리고 parse를 하면 다시 배열로 바꿔준다.
// diaryList를 안건드리고 copyList에 깊은 복사를 하기 위해서 이렇게 한다.
const copyList = JSON.parse(JSON.stringify(diaryList));
const filteredList =
filter === "all" ? copyList : copyList.filter((it) => filterCallBack(it));
const sortedList = filteredList.sort(compare);
return sortedList;
};
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
{getProcesseDiaryList().map((it) => (
<div key={it.id}>
{it.content} {it.emotion}
</div>
))}
</div>
);
};
// 데이터가 정상적으로 전달되지 않으면 빈배열 전달
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
이렇게 감정과 시간을 동시에 정렬할 수 있다.
-새 일기쓰기
<MyButton
type={"positive"}
text={"새 일기쓰기"}
onClick={() => navigate("/new")}
/>
useNavigate를 이용해서 새 일기쓰기 버튼을 만들어준다. 경로는 /new로 가게한다.
이렇게 필터 부분까지 완료되었다.
최종 코드
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
const sortOptionList = [
{ value: "latest", name: "최신순" },
{ value: "oldest", name: "오래된 순" },
];
const filterOptionList = [
{ value: "all", name: "전부다" },
{ value: "good", name: "좋은 감정만" },
{ value: "bad", name: "안좋은 감정만" },
];
// 정렬 필터
// value는 select가 어떤걸 선택하고 있는지
// onChange는 select가 변화했을 때, 바꿀 기능을 하는 함수
// optionList는 select 안에 들어갈 옵션이다.
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();
// 정렬 기준을 정할 state이고 초기값은 최신으로 할거라서 lastest
const [sortType, setSortType] = useState("lastest");
// 감정 숫자 기준으로 출력
const [filter, setFilter] = useState("all");
// 최신순, 오래된 순으로 정렬하는 기능
const getProcesseDiaryList = () => {
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);
}
};
// stringify(diaryList)를 하면 diaryList가 배열에서 문자열로 바뀐다.
// 그리고 parse를 하면 다시 배열로 바꿔준다.
// diaryList를 안건드리고 copyList에 깊은 복사를 하기 위해서 이렇게 한다.
const copyList = JSON.parse(JSON.stringify(diaryList));
const filteredList =
filter === "all" ? copyList : copyList.filter((it) => filterCallBack(it));
const sortedList = filteredList.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>
{getProcesseDiaryList().map((it) => (
<div key={it.id}>
{it.content} {it.emotion}
</div>
))}
</div>
);
};
// 데이터가 정상적으로 전달되지 않으면 빈배열 전달
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
App.css
.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: 10px 20px 10px 20px;
cursor: pointer;
font-family: "Nanum Pen Script";
font-size: 18px;
}
flex-grow:1은 남은 공간의 모든 넓이를 갖게 되어서 왼쪽으로 온다.
이렇게 완성된다.
일기 아이템 컴포넌트
DiaryItem.js를 만들고 DiariyList.js를 수정한다.
-DiaryList.js
{getProcesseDiaryList().map((it) => (
<DiaryItem key={it.id} {...it} />
))}
원래는 {it.content} {it.emotion}으로 불러오던 것을 이렇게 불러온다.
왼쪽 열
-DiaryItem.js
const DiaryItem = ({ id, emotion, content, date }) => {
return (
<div className="DiaryItem">
<div
// 감정에 따라 동적으로 클래스 네임 바꾸기 위해 이렇게 작성
// 쉼표를 제거하기 위해서 join 매소드 사용
class={["emotion_img_wrapper", `emotion_img_wrapper_${emotion}`].join(
" "
)}
>
<img src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`} />
</div>
<div></div>
<div></div>
</div>
);
};
export default DiaryItem;
우선 감정에 따라 동적으로 클래스 네임을 바꿀 수 있도록 만들어준다.
이렇게 감정에 따라 사진이 다르게 나온다.
-App.css
.DiaryItem {
padding-top: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #e2e2e2;
display: flex;
justify-content: space-between;
}
.DiaryItem .emotion_img_wrapper {
cursor: pointer;
min-width: 120px;
height: 80px;
border-radius: 5px;
display: flex;
justify-content: center;
}
.DiaryItem .emotion_img_wrapper_1 {
background-color: #64c964;
}
.DiaryItem .emotion_img_wrapper_2 {
background-color: #9dd772;
}
.DiaryItem .emotion_img_wrapper_3 {
background-color: #fdce17;
}
.DiaryItem .emotion_img_wrapper_4 {
background-color: #fd8446;
}
.DiaryItem .emotion_img_wrapper_5 {
background-color: #fd565f;
}
/* 이미지가 밖으로 안나게 해준다. */
.DiaryItem .emotion_img_wrapper img {
width: 50%;
}
각 이미지 배경에 자신의 색을 주면 네모 모양으로 만들 수 있다.
가운데
const DiaryItem = ({ id, emotion, content, date }) => {
const strDate = new Date(parseInt(date)).toLocaleDateString();
return (
<div className="DiaryItem">
<div
// 감정에 따라 동적으로 클래스 네임 바꾸기 위해 이렇게 작성
// 쉼표를 제거하기 위해서 join 매소드 사용
class={["emotion_img_wrapper", `emotion_img_wrapper_${emotion}`].join(
" "
)}
>
<img src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`} />
</div>
<div className="info_wrapper">
<div className="diary_date">{strDate}</div>
{/* 글이 너무 길면 slice해준다. */}
<div className="diary_content_preview">{content.slice(0, 25)}</div>
</div>
<div></div>
</div>
);
};
export default DiaryItem;
-App.css
.DiaryItem .info_wrapper {
flex-grow: 1;
margin-left: 20px;
cursor: pointer;
}
.DiaryItem .diary_date {
font-weight: bold;
font-size: 25px;
margin-bottom: 5px;
}
.DiaryItem .diary_content_preview {
font-size: 18px;
}
가운데 글자 부분도 완성되었다.
오른쪽
<div className="btn_wrapper">
<MyButton text={"수정하기"} />
</div>
-App.css
/* 화면이 줄어든다고 너무 작아지지 않게 하기 위해서 */
.DiaryItem .btn_wrapper {
min-width: 70px;
}
이렇게 수정하기 버튼도 완성된다.
경로
import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
const DiaryItem = ({ id, emotion, content, date }) => {
const navigate = useNavigate();
const strDate = new Date(parseInt(date)).toLocaleDateString();
// 일기 조회하는 페이지로 이동
const goDetail = () => {
navigate(`/diary/${id}`);
};
// 수정 페이지로 이동
const goEdit = () => {
navigate(`/edit/${id}`);
};
return (
<div className="DiaryItem">
<div
onClick={goDetail}
// 감정에 따라 동적으로 클래스 네임 바꾸기 위해 이렇게 작성
// 쉼표를 제거하기 위해서 join 매소드 사용
class={["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>
{/* 글이 너무 길면 slice해준다. */}
<div className="diary_content_preview">{content.slice(0, 25)}</div>
</div>
<div className="btn_wrapper">
<MyButton onClick={goEdit} text={"수정하기"} />
</div>
</div>
);
};
export default DiaryItem;
goDetail, goEdit를 이용해서 페이지 이동하는 기능까지 구현했다.
'인프런, 유데미 > 한입 크기로 잘라 먹는 리액트' 카테고리의 다른 글
EDIT 구현 (0) | 2022.06.23 |
---|---|
일기 쓰기 구현 (0) | 2022.06.22 |
프로젝트 기초 공사 2 (0) | 2022.06.20 |
프로젝트 기초 공사 1 (0) | 2022.06.19 |
페이지 라우팅 (0) | 2022.06.16 |