DEVELOP
article thumbnail

인프런 이정한님의 한입크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 ] 강의 수강 후 강의의 내용을 정리하며 공부한 것을 쓴 게시글입니다.

 

한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 - 인프런 | 강의

개념부터 독특한 프로젝트까지 함께 다뤄보며 자바스크립트와 리액트를 이 강의로 한 번에 끝내요. 학습은 짧게, 응용은 길게 17시간 분량의 All-in-one 강의!, - 강의 소개 | 인프런...

www.inflearn.com


페이지 구현 - 일기쓰기 ( /New )

- 일기 새로 쓰기 기능과 일기 수정하기 기능의 editor 부분은 동일하기 때문에 DiaryEditor 컴포넌트를 생성하고 각각의 컴포넌트에서 불러오기 할 것 

Header 

- < 버튼은 뒤로가기 기능

      <div>
        <MyHeader
          headText={"새 일기쓰기"}
          leftChild={<MyButton text={"<"} onClick={() => navigate(-1)} />}
        />
      </div>


날짜 선택 section

- 일기 작성하는 날짜는 state로 관리 

- 기본 값은 현재 날짜인 momnet()인데, datetime-local input 과 형식을 맞추기 위해 포맷 설정함 

  const [date, setDate] = useState(new moment().format("YYYY-MM-DDThh:mm"));

 

- type이 datetime-local인 input태그를 추가해 날짜와 시간을 선택할 수 있도록 함 

- 기본값이 현재 날짜와 시간이므로 따로 설정하지 않으면 현재 시간이 뜸 

        <section>
          <h4>오늘은 언제인가요?</h4>
          <div className="input_box">
            <input
              className="input_date"
              type="datetime-local"
              value={date}
              onChange={(e) => setDate(e.target.value)}
            />
          </div>
        </section>


감정 선택 section 

- emotionList 배열 생성 

const emotionList = [
  {
    emotion_id: 1,
    emotion_img: process.env.PUBLIC_URL + "/assets/emotion1.png",
    emotion_description: "완전좋아요",
  },
  {
    emotion_id: 2,
    emotion_img: process.env.PUBLIC_URL + "/assets/emotion2.png",
    emotion_description: "좋아요",
  },
  {
    emotion_id: 3,
    emotion_img: process.env.PUBLIC_URL + "/assets/emotion3.png",
    emotion_description: "그저그래요",
  },
  {
    emotion_id: 4,
    emotion_img: process.env.PUBLIC_URL + "/assets/emotion4.png",
    emotion_description: "나빠요",
  },
  {
    emotion_id: 5,
    emotion_img: process.env.PUBLIC_URL + "/assets/emotion5.png",
    emotion_description: "끔찍해요",
  },
];

- 현재 선택된 emotion을 state로 관리 

  const [emotion, setEmotion] = useState(1);

- 각각의 emotion 선택 버튼을 EmotionItem 컴포넌트를 생성해 관리하고, DiaryEditor에서 불러옴 

              <EmotionItem
                key={it.emotion_id}
                {...it}
                onClick={handleClickEmotion}
                isSelected={it.emotion_id === emotion}
              />

- 선택된 감정으로  setEmotion해주는 함수 handleClickEmotion 선언하고 onClick 함수로 지정

  const handleClickEmotion = (emotion) => {
    setEmotion(emotion);
  };

- EmotionItem 컴포넌트 

  - 감정에 따라서 선택되었을 때 background 색이 다르기 때문에, className으로 emotion_img_wrapper_{emtoion}을 

    추가로 설정

const EmotionItem = ({
  emotion_id,
  emotion_img,
  emotion_description,
  onClick,
  isSelected,
}) => {
  return (
    <div
      onClick={() => onClick(emotion_id)}
        className={[
          "emotion_img_wrapper",
          "emotion_img_wrapper_" + emotion,
        ].join(" ")}
    >
      <img src={emotion_img} alt="face" />
      <span>{emotion_description}</span>
    </div>
  );
};

export default EmotionItem;

- EmotionItem 스타일링

/* EmotionItem */

.EmotionItem {
  cursor: pointer;

  border-radius: 5px;
  padding: 20px 0px 20px 0px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.EmotionItem img {
  width: 60%;
  margin-bottom: 10px;
}

.EmotionItem span {
  font-size: 18px;
}

.EmotionItem_off {
  background-color: #ececec;
}
.EmotionItem_on_1 {
  background-color: #64c964;
  color: white;
}
.EmotionItem_on_2 {
  background-color: #9dd772;
  color: white;
}
.EmotionItem_on_3 {
  background-color: #fdce17;
  color: white;
}
.EmotionItem_on_4 {
  background-color: #fd8446;
  color: white;
}
.EmotionItem_on_5 {
  background-color: #fd565f;
  color: white;
}


일기 작성 section 

- DiaryEditor 컴포넌트에 일기를 작성할 textarea 태그가 있는 section 추가

        <section>
          <h4>오늘의 일기를 작성해주세요.</h4>
          <div className="text_wrapper">
            <textarea
              placeholder="오늘은 어땠나요?"
              ref={contentRef}
              value={content}
              onChange={(e) => setContent(e.target.value)}
            />
          </div>
        </section>

- textarea 스타일링 하기 

.DiaryEditor textarea {
  font-family: "Nanum Myeongjo", serif;
  font-size: 20px;
  width: 100%;
  box-sizing: border-box;
  min-height: 200px;
  resize: vertical;
  border: none;
  background-color: #ececec;
  padding: 20px;
}


완료 section

- 작성한 일기의 길이가 1미만일 떄 textarea에 focus를 주기 위한 Ref 추가

- 현재 일기의 content는 state로 관리 

  const contentRef = useRef();
  const [content, setContent] = useState("");

- DiaryEditor에 완료 button이 있는 section 추가

        <section>
          <div className="submit_btn">
            <MyButton text={"완료"} type="positive" onClick={handleSubmit} />
          </div>
        </section>

- DiaryDispatchContext 에서 onCreate함수를 불러옴

- 완료 버튼을 클릭했을때 처리할 함수 handleSubmit 함수 선언

   - 길이가 1미만일 때 (작성한 내용이 없을 때) textarea에 focus 주며 return

   - 작성한 내용이 있을 때 홈 화면으로 이동 ( replace:true 설정하면 다시 뒤로가기 불가)

   - onCreate 함수에 date, content, emotion 전달 

  const { onCreate } = useContext(DiaryDispatchContext);
  const handleSubmit = () => {
    if (content.length < 1) {
      contentRef.current.focus();
      return;
    }
    navigate("/", { replace: true });
    onCreate(date, content, emotion);
  };


페이지 구현 - 일기 수정( /Edit )

Edit 컴포넌트

- 일기 리스트에서 수정하기 버튼을 누르면 /Edit/{id} 로 이동함 

- Edit 컴포넌트에서 id 값 받아오기 

/* App.js */
  <Route path="/edit/:id" element={<Edit />} />
              
/* Edit 컴포넌트 */
  const { id } = useParams();

- Edit 컴포넌트에서 diaryList 불러오기 

  const diaryList = useContext(DiaryStateContext);

- 수정 중인 일기의 원본 originData를 state로 관리 

  const [originData, setOriginData] = useState();

- useEffect로 diaryList나 id값이 변경되었을 때 아래 함수 실행되도록 함 

- diaryList의 길이가 0보다 크지 않으면 (없으면) 홈 화면으로 이동하게 함 

- diaryList의 길이가 0보다 크면 diaryList 중 id값이 수정 중인 다이어리의 id와 같은 것을 찾아 targetDiary에 저장함

- targetDiary가 있으면 setOriginData하고, 없으면 홈 화면으로 이동 

  useEffect(() => {
    if (diaryList.length > 0) {
      const targetDiary = diaryList.find(
        (it) => parseInt(id) === parseInt(it.id)
      );
      if (targetDiary) {
        setOriginData(targetDiary);
      } else {
        navigate("/", { replace: true });
      }
    } else {
      navigate("/", { replace: true });
    }
  }, [diaryList, id]);

- originData가 truty면 DiaryEditor 컴포넌트에 isEdit:true와 originData를 전달하여 호출함 

  return (
    <div>
      {originData && <DiaryEditor isEdit={true} originData={originData} />}
    </div>
  );

DiaryEditor 컴포넌트 수정 

- handleSubmit 함수 수정 

- isEdit은 수정 중인지, 새 일기 작성 중인지를 구분하기 위한 변수 

- isEdit이 true이면 " 수정된 일기를 저장할까요?"를 confirm으로 띄우고, false이면 " 새 일기를 저장힐까요?"를 confirm으로 띄움 

- 사용자가 확인 버튼을 눌렀을 떄 isEdit이 true이면 onEdit에 originDate.id, date,content,emotion을 전달

- false이면 onCreate에 date, content, emotion을 전달 

  const handleSubmit = () => {
    if (content.length < 1) {
      contentRef.current.focus();
      return;
    }
    if (window.confirm((isEdit ? "수정된 " : "새 ") + "일기를 저장할까요?")) {
      if (isEdit) {
        onEdit(originData.id, date, content, emotion);
      } else {
        onCreate(date, content, emotion);
      }
    }
    navigate("/", { replace: true });
  };

- useEffect 로 isEdit 값과 originData값이 변경될 때마다 아래 함수 실행되도록 함 

- isEdit이 true 이면 수정하는 중이므로, 에디터의 날짜와 감정, 내용을 원본으로 띄우도록 함 

  useEffect(() => {
    if (isEdit) {
      setDate(moment(originData.date).format("YYYY-MM-DDThh:mm"));
      setEmotion(originData.emotion);
      setContent(originData.content);
    }
  }, [isEdit, originData]);

- 헤더 수정하기

        <MyHeader
          headText={isEdit ? "일기 수정하기" : "새 일기 작성하기"}
          leftChild={<MyButton text={"<"} onClick={() => navigate(-1)} />}
        />


페이지 구현 - 일기 상세 ( /Dairy )

- 홈 화면에서 다이어리 아이템을 클릭하면 해당 일기의 상세 페이지 (/Diary/{id}) 로 이동함

- id값을 받아오기 위해  useParams 사용하고, diaryLlist를 DiaryStateContext에서 받아옴

    const { id } = useParams();
    const diaryList = useContext(DiaryStateContext);

- 상세페이지를 보여줄 다이어리 data를 state로 관리

  const [data, setData] = useState();

- useParams로 받은 id값과 같은 id를 가지고 있는 다이어리를 find로 찾아서 targetDiary에 저장하고, setData함 

- 없으면 home화면으로 이동 

  useEffect(() => {
    if (diaryList.length > 0) {
      const targetDiary = diaryList.find(
        (it) => parseInt(id) === parseInt(it.id)
      );
      if (targetDiary) {
        setData(targetDiary);
      } else {
        alert("존재하지 않는 일기입니다.");
        navigate("/", { replace: true });
      }
    }
  }, [id, diaryList]);

- emotionList에서 현재 다이어리의 emotion값을 find해서 curEmotionData에 저장

    const curEmotionData = emotionList.find(
      (it) => parseInt(it.emotion_id) === parseInt(data.emotion)
    );

헤더 secrion 

- 헤더의 text를 현재 날짜로 하고, 왼쪽에는 뒤로가기 버튼을, 오른쪽에는 수정하기 버튼을 추가함 

        <MyHeader
          headText={`${moment(data.date).format("YYYY년 MM월 DD일 dddd")}`}
          leftChild={<MyButton text="<" onClick={() => navigate(-1)} />}
          rightChild={
            <MyButton
              text="수정하기"
              onClick={() => navigate("/edit/" + data.id)}
            />
          }
        />

감정 section

- 헤더 바로 아래에 오늘의 감정을 이미지와 text로 띄움 

          <section>
            <h4>오늘의 감정</h4>
            <div
              className={[
                "diary_img_wrapper",
                `diary_img_wrapper_${data.emotion}`,
              ].join(" ")}
            >
              <img src={curEmotionData.emotion_img} />
              <div className="emotion_description">
                <span>{curEmotionData.emotion_description}</span>
              </div>
            </div>
          </section>

- 감정 section 스타일링

.Diary .diary_img_wrapper {
  background-color: #ececec;
  width: 250px;
  height: 250px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
}

.Diary .diary_img_wrapper_1 {
  background-color: #64c964;
  color: white;
}
.Diary .diary_img_wrapper_2 {
  background-color: #9dd772;
  color: white;
}
.Diary .diary_img_wrapper_3 {
  background-color: #fdce17;
  color: white;
}
.Diary .diary_img_wrapper_4 {
  background-color: #fd8446;
  color: white;
}
.Diary .diary_img_wrapper_5 {
  background-color: #fd565f;
  color: white;
}
.Diary .emotion_description {
  font-size: 20px;
}

내용 section 

          <section>
            <h4>오늘의 일기</h4>
            <div className="diary_content_wrapper">
              <p>{data.content}</p>
            </div>
          </section>

profile

DEVELOP

@JUNGY00N