[React] 페이지 구현 - 홈(‘/’)
1️⃣ 버튼 및 공통 레이아웃 만들기
MyHeader.js
// src > components > 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;
/* src > 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 Pen Script";
}
MyButton.js
// src > components > MyButton.js
const MyButton = ({ text, type, onClick }) => {
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;
type에 따라서 css 디자인을 통해 다른 색깔이 나오도록 함.
/* src > App.css */
/* MyButton */
.MyButton {
cursor: pointer;
border: none;
border-radius: 5px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
font-size: 18px;
white-space: nowrap;
font-family: "Nanum Pen Script";
}
.MyButton_default {
background: #ececec;
color: black;
}
.MyButton_positive {
background: #64c064;
color: white;
}
.MyButton_negative {
background: #fd565f;
color: white;
}
2️⃣ 헤더 만들기
// src > pages > Home.js
import { useState } from "react";
// COMPONENTS
import MyHeader from "./../components/MyHeader";
import MyButton from "./../components/MyButton";
const Home = () => {
const [curDate, setCurDate] = useState(new Date());
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
// 버튼 클릭 -> 월이 바뀔 수 있도록 하는 메서드
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={decreaseMonth} />}
rightChild={<MyButton text={">"} onClick={increaseMonth} />}
></MyHeader>
</div>
);
};
export default Home;
3️⃣ 임시로 App 컴포에 dummyList 넣기
// src > App.js
const dummyData = [
{
id: 1,
emotion: 1,
content: "오늘의 일기 1번",
date: 1658666575617,
},
{
id: 2,
emotion: 2,
content: "오늘의 일기 2번",
date: 1658666575619,
},
{
id: 3,
emotion: 3,
content: "오늘의 일기 3번",
date: 1658666575621,
},
{
id: 4,
emotion: 4,
content: "오늘의 일기 4번",
date: 1658666575623,
},
{
id: 5,
emotion: 5,
content: "오늘의 일기 5번",
date: 1658666575625,
},
];
// data 기본값을 dummyData로 변경
const [data, dispatch] = useReducer(reducer, dummyData);
4️⃣ Home 컴포에서 dummyData 사용
일기가 작성된 날짜에 따라서 해당 월에 작성된 일기만 출력해야 되므로 Date 객체를 사용해서 일기가 작성될 날짜를 구한다.
const Home = () => {
const diaryList = useContext(DiaryStateContext);
// 현재 월에 해당하는 일기만 필요하므로 useState 사용
const [data, setData] = useState([]);
const [curDate, setCurDate] = useState(new Date());
// 해당 달에 작성된 일기만 추리기 위한 코드
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.date <= lastDay)
);
}
}, [diaryList, curDate]);
...
export default Home;
일기 컴포넌트 만들기
components 폴더에 DiaryList.js 라는 컴포넌트를 만든다.
DiaryList 컴포넌트는 더미 리스트의 데이터를 하나의 리스트처럼 묶어서 사용할 수 있도록 만든 컴포넌트 이다.
// src > components > DiaryList.js
const DiaryList = ({ diaryList }) => {
return (
<div>
{diaryList.map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
DiaryList.defaultProps = {
dirayList: [],
};
export default DiaryList;
컴포넌트를 만들었으니까 Home 컴포넌트에 연결을 해주고, 추가해준다.
return (
<div>
<MyHeader
headText={headText}
leftChild={<MyButton text={"<"} onClick={decreaseMonth} />}
rightChild={<MyButton text={">"} onClick={increaseMonth} />}
></MyHeader>
<DiaryList diaryList={data}></DiaryList>
</div>
);
DiaryList 정렬
헤더 밑에 있는 버튼 같은거! 최근순, 오래된 순, 좋은 감정, 안좋은 감정을 필터링 할 수 있는 기능을 만들 것이다.
-
최신순, 오래된 순 option 만들기
// src > components > DiaryList.js import { useState } from "react"; 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 sortOptionList = [ { value: "latest", name: "최신순" }, { value: "oldest", name: "오래된 순" }, ]; const DiaryList = ({ diaryList }) => { const [sortType, setSortType] = useState("latest"); return ( <div> <ControlMenu value={sortType} onChange={setSortType} optionList={sortOptionList} ></ControlMenu> {diaryList.map((it) => ( <div key={it.id}>{it.content}</div> ))} </div> ); }; DiaryList.defaultProps = { dirayList: [], }; export default DiaryList;
- value Prop: 어떤 종류의 select를 골랐는지
- onChange Prop: select가 정의한 값이 바뀌었을 때 기능하는 함수
- option Prop: select 태그 안에 들어있는 리스트
❓ key={idx}가 필요한 이유?
선택을 했으면 알아서 sorting이 되어야하기 때문에 DiaryList 컴포넌트에 다음과 같은 메소드를 만들어 준다.
const getProcessedDiaryList = () => { const copyList = JSON.parse(JSON.stringify(diaryList)); };
이렇게 된다면 diaryList -> stringify(문자열로 만들어줌) -> parse(다시 배열로 복구) -> copyList로 전달
이렇게 해주었다면 date 비교를 통해서 sort 를 해준다.
const getProcessedDiaryList = () => { 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)); const sortedList = copyList.sort(compare); return sortedList; };
그리고 getProcessedDiaryList 함수를 통해서 리스트가 출력되도록 하면 적용된 것을 확인할 수 있다.
return ( <div> <ControlMenu value={sortType} onChange={setSortType} optionList={sortOptionList} ></ControlMenu> {getProcessedDiaryList().map((it) => ( <div key={it.id}>{it.content}</div> ))} </div> );
-
좋은 감정, 안좋은 감정 sort
const filterOptionList = [ { value: "all", name: "모두다" }, { value: "good", name: "좋은 감정만" }, { value: "bad", name: "안좋은 감정만" }, ]; const DiaryList = ({ diaryList }) => { const [sortType, setSortType] = useState("latest"); const [filter, setfilter] = useState("all"); ... return ( <div> <ControlMenu value={sortType} onChange={setSortType} optionList={sortOptionList} ></ControlMenu> // 추가 <ControlMenu value={filter} onChange={setfilter} optionList={filterOptionList} ></ControlMenu> {getProcessedDiaryList().map((it) => ( <div key={it.id}> {it.content} {it.emotion} </div> ))} </div> );
좋은 감정, 안 좋은 감정만 sort 하는 방법은 반복문을 돌면서 true로 리턴되는 애들만으로 필터해주면 된다.
const getFilterCallBack = (item) => { if (filter === "good") { return parseInt(item.emotion) <= 3; } else { return parseInt(item.emotion) > 3; } }; const getProcessedDiaryList = () => { 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)); const filteredList = filter === "all" ? copyList : copyList.filter((it) => getFilterCallBack(it)); const sortedList = filteredList.sort(compare); return sortedList; };
-
새일기 쓰기 버튼 만들기
기존에 만들어 두었던 MyButton 컴포넌트를 이용하고, 초록색으로 나오게 하고 싶으니까 type={“positive”}, text={“새 일기 쓰기”}를 해준다. 그리고 onClick을 했을 때 new 페이지로 이동을 해야되니까 전에 했던 useNavigate 함수를 이용한다.
return ( <div className="DiaryList"> <div className="menu_wrapper"> <div className="left_col"> <ControlMenu value={sortType} onChange={setSortType} optionList={sortOptionList} ></ControlMenu> <ControlMenu value={filter} onChange={setfilter} optionList={filterOptionList} ></ControlMenu> </div> <div className="right_col"> // 추가 <MyButton type={"positive"} text={"새 일기 쓰기"} onClick={() => navigate("./new")} ></MyButton> </div> </div> {getProcessedDiaryList().map((it) => ( <div key={it.id}> {it.content} {it.emotion} </div> ))} </div> );
css 적용을 위해서 className 지정
/* DiaryList */ .DiaryList .menu_wrapper { margin-top: 30px; 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; }
DiaryList -> DiaryItem
// src > components > DiaryItem.js
import MyButton from "./MyButton";
const DiaryItem = ({ id, emotion, content, date }) => {
const strDate = new Date(parseInt(date)).toLocaleDateString();
return (
<div className="DiaryItem">
<div
className={[
"emotion_img_wrapper",
`emotion_img_wrapper_${emotion}`,
].join(" ")}
>
<img
src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`}
alt=""
/>
</div>
<div 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 text={"수정하기"}></MyButton>
</div>
</div>
);
};
export default DiaryItem;
코드 형태만 보면 그렇게 어렵지 않다. css가 많이 차지!
/* App.css */
/* DiaryItem */
.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: #fd585f;
}
.DiaryItem .emotion_img_wrapper img {
width: 50%;
}
.DiaryItem .info_wrapper {
flex-grow: 1;
cursor: pointer;
margin-left: 20px;
}
.DiaryItem .diary_date {
font-weight: bold;
font-size: 25px;
margin-bottom: 5px;
}
.DiaryItem .diary_content_preview {
font-size: 10px;
}
.DiaryItem .btn_wrapper {
min-width: 70px;
}
그리고 버튼 기능 구현 => 당연히 다른 페이지로 이동을 해야되니까 useNavigate 훅을 사용한다.
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(`/diary/${id}`);
};
return (
<div className="DiaryItem">
<div
onClick={goDetail}
className={[
"emotion_img_wrapper",
`emotion_img_wrapper_${emotion}`,
].join(" ")}
>
<img
src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`}
alt=""
/>
</div>
<div className="info_wrapper">
<div className="diary_date">{strDate}</div>
<div className="diary_content_preview">{content.slice(0, 25)}</div>
</div>
<div onClick={goEdit} className="btn_wrapper">
<MyButton text={"수정하기"}></MyButton>
</div>
</div>
);
};
export default DiaryItem;
댓글남기기