홈화면에서는 헤더에 날짜, 그리고 < > 버튼을 누르면 이전 달로 이동하도록 만들고,
또 일기 리스트는 필터도 적용한다.
Home.js
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
일단 헤더의 가운데 글씨 부분을 설정
근데 자바스크립트의 getMonth()함수는 이상해서 그냥 실행하면 첫달이 1이 아니고 0이 된다고 한당
그래서 +1 을 해주어야 함.
그리고 이제 버튼 양 옆을 누르면 이전 달로 이동해야함.
그래서 현재 헤더에 표시된 날짜를 저장하는 state를 생성한다.
//헤더 날짜 저장하는 state
const [curDate, setCurDate] = useState(new Date());
그러고 나서 월 증가/감소 함수를 만들어 줌.
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();
}}
/>
}
/>
</div>
);
...
일단 필터는 두 종류인데,
App.js
const dummyData = [
{
id: 1,
emotion: 1,
content: "오늘의 일기1",
date: 1678172343286,
},
{
id: 2,
emotion: 2,
content: "오늘의 일기2",
date: 1678172343287,
},
{
id: 3,
emotion: 3,
content: "오늘의 일기3",
date: 1678172343288,
},
{
id: 4,
emotion: 4,
content: "오늘의 일기4",
date: 1678172343289,
},
{
id: 5,
emotion: 5,
content: "오늘의 일기5",
date: 1678172343290,
},
];
date 값은 ms값으로 넣어야 하기 때문에,
newDate().getTime() 을 콘솔에 찍어서 확인한다.
console.log(new Date().getTime())
하나만 넣어주고 나머지는 숫자 1씩 올렸다. 그래야 정렬을 해볼 수 있으니깐!
그리고 원래 useReducer(reducer, [])
였던 부분에
dummyData를 기초값으로 넣어준당
const [data, dispatch] = useReducer(reducer, dummyData);
Home.js
const diaryList = useContext(DiaryStateContext);
이제 근데 다이어리 게시글이 월이 바뀌면 보이지 않아야 한다.
나는 지금 더미데이터를 2023.3월 기준으로 만들었으므로, 2월 4월에는 보이지 않게 해야함..
즉 월 별로 다이어리리스트를 맞는 날짜에 보이게 하기 위한 작업을 해야 한다.
const [data, setData] = useState([]);
useEffect
를 이용해서 만들어 줌.
여담인데 date랑 data랑 헷갈려서 미치게따 ㅎ_ㅎㅋㅋㅋㅋㅋ
useEffect(() => {
if (diaryList.length >= 1) {
// 그냥 진행되면 매우 오래 걸리는 작업이므로 list가 없을 땐 실행 x
const firstDay = new Date(
curDate.getFullYear(),
curDate.getMonth(),
1
).getTime();
// 매달의 첫 번째 날을 ms로 구해 줌
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth() + 1,
0
).getTime();
// 매달의 마지막 날을 구해줌
setData(
diaryList.filter((it) => firstDay <= it.date && it.date <= lastDay)
);
}
}, [curDate]);
여기까지 하면 오류가 생긴다. diaryList 빼먹은거아냐~? 하고 알려줌
만약에 [curDate]
만 있다면, diaryList가 변경되었을 땐(글 추가,수정, 삭제 등)실행이 되지가 않기 때문에 꼭 diaryList를 넣어주어야 한다.
...
}, [diaryList, curDate]);
이 데이터 5개 중 하나의 날짜를 바꾸어 보면,
(비교 쉬우라고 원래 시간은 주석처리하고 숫자 바꿈 -> 매우매우 미래가 되었다)
3월 리스트에서 일기 하나가 빠진 걸 볼 수 있다. 눌러보면 5번은 여기 없음.
이제 다이어리 목록을 component로 만들어주고 Home에 뿌려줌
components/DiaryList.js
const DiaryList = ({ diaryList }) => {
return (
<div>
{diaryList.map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
export default DiaryList;
pages/Home.js
...
<DiaryList diaryList={data} />
그리고 추가로
DiaryList.defaultProps = {
diaryList: [],
};
도 해주었는데 defaultProps 에 대한 자세한 정보는 여기에서... 하기로 함!
DiaryList.js
const sortOptionList = [
{ value: "latest", name: "최신 순" },
{ value: "oldest", name: "오래된 순" },
];
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>
);
};
value
: select에서 현재 어떠한 역할인지에 대한 정보
onChange
: select서 선택하는게 변화하였을 때 바꿀 기능을 할 함수
optionList
: <select>
안에 들어갈 옵션 (최신 순, 오래된 순)
여기서 <select value={value} 를 써주는 이유
- state를 이용한 select 태그의 초기값 설정이 가능하기 때문.
- 예를 들어 select 태그가 화면에 렌더링 되자 마자 특정 값이 선택된 상태를 구현하고자 하는 상황에 이용할 수 있다.
const DiaryList = ({ diaryList }) => {
const [sortType, setSortType] = useState("latest");
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
...
select
내에서 onChange
이벤트가 발생하게 되면, 이벤트 객채의 target.value를 전달을 해서
prop으로 받은 onChange
메서드를 실행시키는데, 그 onChange 메서드는 setSortType
이었기 때문에
오래된 순을 선택하게 되면 oldest가 되게 되고, 최신 순을 선택하게 되면 latest가 된다.
이제 리스트가 바뀌는 걸 구현해보쟈
const getProcessDiaryList = () => {
const compare = (a, b) => {
if (sortType === "latest") {
return parseInt(b.date) - parseInt(a.date);
// 문자열이 들어올 수도 있기 때문에 parseInt를 해주어야 함 (숫자로 바꾸어 줌)
} else {
return parseInt(a.date) - parseInt(b.date);
}
};
const copyList = JSON.parse(JSON.stringify(diaryList));
const sortedList = copyList.sort(compare);
return sortedList;
};
sort()
를 쓰면 원본배열이 변화하기 때문에 배열을 copy. 깊은 복사를 해준다(copyList)
JSON.stringify(diaryList)
는 diaryList를 문자화시킴
그걸 JSON.parse
로 실행시키면 다시 배열로 됨 그래서 배열 자체 모양은 건드려지지 않은 채 깊은 복사가 됨.
혹시 스프레드 연산자로 가능할까?
- 스프레드 연산자도 깊은 복사의 방법 중 하나이지만, 이는 1차원 배열 or 객체에만 해당되기 때문에 다차원의 데이터에서는 JSON을 이용해야 한다. (
lodash
라는 라이브러리도 있다고 함)
그리고 리스트 map으로 뿌려줬던 부분을
{getProcessDiaryList().map((it) => (
<div key={it.id}>{it.content}</div>
))}
로 바꾸어 준다.
함수니까 getProcessDiaryList()에서 ()
꼭 빼먹지 말기~
const [filter, setFilter] = useState("all");
const filterOptionList = [
{ value: "all", name: "전부 다" },
{ value: "good", name: "좋은 감정만" },
{ value: "bad", name: "나쁜 감정만" },
];
위에서 만들었던 컨트롤메뉴에 prop 받아서 넣어준다
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
아 그리고 리스트에 감정이 있어야 구분이 쉬우니까 {it.emotion}
도 추가해 줌ㅋㅋ
filterList 를 구현해 주는데,
만약에 filter가 all 일 경우, copyList를 반환하고, 아닐 경우에는 copyList를 필터한 것을 반환한다.
이때 필터는 또 함수를 하나 생성해서,
filter가 good이면 감정이 1,2 인 (임시) 글을, bad 이면 4,5인 글을 필터링하게 함.
그리고 위에서 만들었던 sortedList도 바꾸어 준다.
이것들을 실행한 코드
const getProcessDiaryList = () => {
const filterCallback = (item) => {
if (filter === "good") {
return parseInt(item.emotion) <= 2;
} else if (filter === "bad") {
return parseInt(item.emotion) >= 4;
}
};
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) => filterCallback(it));
const sortedList = filteredList.sort(compare);
return sortedList;
};
copyList.filter((it) => filterCallback(it))
이부분은 it이 filterCallback을 받았을 때 return true를 반환하게 하는 애들로 필터링 해주는 것이다.
만약 !filterCallback(it)
이라고 적으면 나쁜 감정일 때 123, 좋은 감정일 때 345가 나옴.
useNavigate를 이용한다.
const navigate = useNavigate();
...
return (
<MyButton
type={"positive"}
text={"새로운 일기쓰기"}
onClick={() => {
navigate("/new");
}}
/>
...
)
이제 이 버튼을 누르면 새 글 작성하는 페이지로 이동함! 이건 매우매우 매우 간단!
DiaryList.js
{getProcessDiaryList().map((it) => (
<DiaryItem key={it.id} {...it} />
))}
일단 DiaryItem 컴포넌트에서 받아와야 할 prop는 { id, emotion, content, date }
이렇게 네 개 이다.
DiaryItem.js
<div className={["emotion_img", `emotion_img_${emotion}`].join(" ")}>
<img src={process.env.PUBLIC_URL + `/assets/emotion${emotion}.png`} />
</div>
emotion_img_${emotion}
이걸 붙여준 이유는 다섯 개의 이미지를 다 각자 선택해서 css를 수정하기 위해서이다.
사진하나를 console.log에 찍어보게 되면
이렇게 나오고, join(" ") 덕분에 중간의 쉼표도 나오지 않는다.
다른 사진들을 눌러보면 뒤의 숫자만 1, 2, 3, .. 하고 바뀜.
근데 나는 이 기능을 사실상 이용할 일이 없어서 알아만 두려고 적어 놓는다.
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || "";
을 위에 넣어준다.
그리고 css 어느정도 손보고 넘어가자
const strDate = new Date(parseInt(date)).toLocaleDateString();
..
<div className="diary_date">{strDate}</div>
<div className="diary_content_prev">{content.slice(0, 25)}</div>
<div className="btn_wrap">
<MyButton text={"수정하기"} />
</div>
프리뷰이기 때문에 n자까지만 잘라서 보여주게 만들었다.
그래서 더미데이터도 수정함
const goDetail = () => {
navigate("/diary/${id}");
};
const goEdit = () => {
navigate("/edit/${id}");
};
해서 각각 div에 onClick 이벤트로 삽입해준다.
홈까지 수정한 내용이다.
내일 prev에 ...을 뒤에 넣어보려고 한당 !!
오늘 기능 구현 다 넣으려했는데
이것저것 찾아보고 블로깅하느라고 시간이 꽤 오래 걸렸다 ㅠ.ㅠ 아쉬워
오늘하루만에 많은걸 하셨는걸요 ? 구현방법 자세히 설명해줘서 알기 쉬웠습니다!