반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

HOME 구현하기 본문

인프런, 유데미/한입 크기로 잘라 먹는 리액트

HOME 구현하기

5_hyun 2022. 6. 21. 17:25

헤더

-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를 이용해서 페이지 이동하는 기능까지 구현했다.

728x90
반응형

'인프런, 유데미 > 한입 크기로 잘라 먹는 리액트' 카테고리의 다른 글

EDIT 구현  (0) 2022.06.23
일기 쓰기 구현  (0) 2022.06.22
프로젝트 기초 공사 2  (0) 2022.06.20
프로젝트 기초 공사 1  (0) 2022.06.19
페이지 라우팅  (0) 2022.06.16
Comments