# 프로젝트 기초 공사 2
전체 프로젝트 구조를 살펴보자
각각의 컴포넌트는 다른 state가 필요하다
우선 App 컴포넌트에서 일기 데이터를 관리할 수 있는 state 를 만들어보자
# 상태 관리 세팅하기
useReducer 를 사용해서 App 컴포넌트에서 상태 관리를 세팅하자
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 state;
};
reducer 를 생성해준다
각각의 case 별로 어떤 작업을 수행할지 정리한다
# dispatch 함수 생성
만들어준 reducer에 맞춰서 dispatch 함수를 만들어준다
data의 id 값은 useRef 를 사용해서 받는다
const dataId = useRef(0);
# CREATE 함수
const onCreate = (date, content, emotion) => {
dispatch({
type: "CREATE",
data: {
id: dataId.current,
data: 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,
},
});
};
이렇게 작성한 상태 관리 로직의 context를 만들어서 data state를 컴포넌트 트리 전역에 공급해보자
# Context 생성
App 컴포넌트 상단에 context 컴포넌트를 생성하고 return 문의 모든 컴포넌트를
DiaryStateContext.Provider 컴포넌트로 감싼다
그리고 value 값으로는 data를 보내주면 전역에서 사용가능하다
export const DiaryStateContext = React.createContext();
//return
<DiaryStateContext.Provider value={data}>
<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>
</DiaryStateContext.Provider>
onCreate, onRemove, onEdit 함수를 관리하는 DiaryDispatchContext 컴포넌트도 생성해서
DiaryStateContext 컴포넌트 태그의 하위에 넣어주고 value 값으로 각 함수를 보내주자!
# HOME 구현하기
# Header
화면이 마운트됨과 동시에 상단 헤더에는 현재의 년, 월이 표시되어야 한다
또한 이전달, 다음달로 이동할 수 있는 버튼이 필요하다
1. 년 월 띄우기
날짜를 생성하는 state를 생성해서 MyHeader 컴포넌트에 전달하자
getMonth() 는 1월을 0월로 반환하는 것에 주의해야 한다
const [curDate, setCurDate] = useState(new Date());
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
return (
<div>
<MyHeader headText={headText} />
</div>
);
};
2. 이전달 다음달 이동 버튼
왼쪽 버튼을 누르면 이전달로, 오른쪽 버튼을 누르면 다음달로 이동할 수 있는 함수를 구현하여
각각의 버튼 컴포넌트에 연결하자
const increaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
);
};
const decreaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
);
};
해당 함수들을 이전에 만들어놓았던 MyButton 컴포넌트에 연결하니
빠르고 쉽게 구현 가능하다
# DiayList 컴포넌트 구현
우선 화면이 렌더링될 때 해당 년, 월에 해당하는 일기 리스트들만 보이도록 구현해야 한다
전체적인 구현과정을 정리해보겠다
1. useContext 사용해서 더미 데이터를 diaryList 에 담아오기
2. DiaryList 컴포넌트 생성 후 diaryList props 로 보내주기
3. DiaryList 컴포넌트에서 해당 diaryList data 리스트 형태로 뿌려주기
1. useContext 사용해서 더미 데이터를 diaryList 에 담아오기
DiaryList 컴포넌트를 렌더링하기 위해 App 컴포넌트에 dummyData를 생성하고 해당 값들을 data에 넣어준다
Home 컴포넌트에서 useContext 를 사용해서 해당 더미값을 받아온다
더미 값을 제대로 받아오고 있다!
화면이 렌더링될 때 현재 년, 월에 해당하는 일기 데이터만 가져와야 한다
따라서 useEffect 사용해서 curDate 가 변하면 리스트가 바뀌도록 로직을 작성해보자
일단 매 달의 첫번째 날짜와 마지막 날짜 구하자
첫번째 날짜 ~ 마지막 날짜 사이의 일기 데이터만 보이도록 하면 된다
useEffect(() => {
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]);
2. DiaryList 컴포넌트 생성 후 diaryList props 로 보내주기
3. DiaryList 컴포넌트에서 해당 diaryList data 리스트 형태로 뿌려주기
DiaryList 컴포넌트를 생성해서 diaryList를 props 로 받아 map으로 뿌리도록 로직을 작성한다
import React from "react";
const DiaryList = ({ diaryList }) => {
return (
<div>
{diaryList.map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
# DiaryList 정렬 기능
# 최신순, 오래된순 필터
ControlMemu 컴포넌트를 추가한다
const ControlMenu = ({ value, onChange, optionList }) => {
return (
<select value={value} onChange={(e) => e.target.value}>
{optionList.map((it, idx) => (
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
};
최신순, 오래된 순 고를 수 있는 옵션 리스트를 추가한다
const sortOptionList = [
{ value: "lastest", name: "최신순" },
{ value: "oldest", name: "오래된 순" },
];
기존의 diaryList 배열 중 가장 오래된 순서 또는 최신 순서로 보여주어야 한다
기존 배열은 건들지 않아야 하기 때문에 깊은 복사가 필요하다
가장 쉬운 깊은 복사 방법
JSON.parse(JSON.stringify ) 를 사용하자!!
const copyList = JSON.parse(JSON.stringify(diaryList));
날짜를 비교하는 compare 함수를 만들어서 해당 함수를 기준으로 sort 하자
const getProcessedDiaryList = () => {
const compare = (a, b) => {
if (sortType === "lastest") {
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;
};
이제 만들어준 함수로 map을 수행시키면 원하는대로 날짜 기준으로 정렬이 된다
{getProcessedDiaryList().map((it) => (
<div key={it.id}>{it.content}</div>
))}
ControlMemu 컴포넌트를 추가해서 위의 로직에 맞추어 props 를 전달하자
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
최신순으로 정렬했을 때
오래된 순으로 정렬했을 때
정렬이 잘 되고 있다
2. 감정 점수 필터
시간순 필터링과 동일한 방식으로 구현한다
useState를 사용해서 현재의 상태를 받아오고 select 태그를 사용해서 화면에 띄워질 수 있도록 감정 필터 옵션 리스트를 만들자
const filterOptionList = [
{ value: "all", name: "전부다" },
{ value: "good", name: "좋은 감정만" },
{ value: "bad", name: "안좋은 감정만" },
];
해당 리스트를 사용하는 감정 필터를 생성한다
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
기존에 시간 순으로 정렬했던 리스트에 다시 한번 감정 점수를 기준으로 정렬하는 것이 필요하다
따라서 이전의 getProcessedDiaryList 함수 내에서 수정하자
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 === "lastest") {
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) => filterCallBack(it));
const sortedList = filteredList.sort(compare);
return sortedList;
};
감정 상태를 기준으로 filter를 한 뒤에 해당 값으로 다시 한번 compare 함수를 적용한다
어렵다..! 여러번 복습하자!
# 새 일기 쓰기 기능 구현
MyButton 컴포넌트를 사용해서 새로운 일기 쓰기 페이지로 이동하는 버튼을 추가하자
이때 useNavigate 가 사용된다
<MyButton
type={"positive"}
text={"새 일기쓰기"}
onClick={() => navigate("/new")}
/>
잘 완성된 모습이다
css 적용 후의 모습은 아래와 같다
# DiaryItem 컴포넌트 생성
일기 리스트 각각을 보여줄 컴포넌트를 생성해보자
총 3개의 div로 분할하여
1. 감정에 따른 이미지 보여줄 div
2. 간략한 일기 내용 보여줄 div
3. 수정하기 버튼 div
로 구성한다
1. 감정에 따른 이미지 div
<div
className={[
"emotion_img_wrapper",
`emotion_img_wrapper_${emotion}`,
].join(" ")}
>
<img src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`} />
</div>
각각의 emotion 이미지에 맞춰서 css 스타일링을 적용해주면 아래와 같이 잘 띄워지는 것을 확인할 수 있다
2. 간략한 일기 내용 보여줄 div
<div className="info_wrapper" onClick={goRetail}>
<div className="diary_date">{strDate}</div>
<div className="diary_content_preview">{content.slice(0, 25)}</div>
</div>
3. 수정하기 버튼 div
<div className="btn_wrapper">
<MyButton text={"수정하기"} onClick={goEdit} />
</div>
해당 영역 눌렀을 때 페이지 이동까지 할 수 있도록 useNavigate 사용하자
const goRetail = () => {
navigate(`/diary/${id}`);
};
const goEdit = () => {
navigate(`/edit/${id}`);
};
최종적으로 완성된 모습이다!
# 새로 알게 된 점
이번 강의를 통해서 정렬 필터를 만드는 방법을 새로 알게 되었다
필터를 만들기 위해서는 어떤 식으로 데이터를 받아오고 있는지 확실하게 파악해야 한다
또한 그 데이터를 활용해서 어떻게 로직을 작성할 것인지 알아야 한다
compare 함수를 통해서 원하는 식으로 정렬하는 방법도 새로 알게 되었는데
이 방법은 다양한 상황에 적용할 수 있을 것 같다
유용한 내용이었지만 처음 구현하는 것이라 헷갈리기 때문에 리팩토링하면서 다시 한번 복습해야겠다
'TIL > 프로그래머스 데브코스' 카테고리의 다른 글
클라우딩 어플리케이션 엔지니어링 TIL Day 36 - 감정 일기장 만들기 (5) (0) | 2024.02.29 |
---|---|
클라우딩 어플리케이션 엔지니어링 TIL Day 35 - 감정 일기장 만들기 (4) (0) | 2024.02.27 |
클라우딩 어플리케이션 엔지니어링 TIL Day 33 - 감정 일기장 만들기 (2) (0) | 2024.02.23 |
클라우딩 어플리케이션 엔지니어링 TIL Day 32 - 감정 일기장 만들기 (1) (0) | 2024.02.23 |
클라우딩 어플리케이션 엔지니어링 TIL Day 31 - 리액트 기본 간단한 일기장 만들기 (4) (2) | 2024.02.23 |