7강에서 버그 수정하기 전까지 강의를 수강해놓고, 기말고사와 해커톤 대회 운영을 한다고 잠시 쉬었다가 왔다. 2주 정도 안했다고 진짜 하나도 기억이 안나는 것 같은 기분이지만,, 다행히 정리해뒀던 벨로그 글들을 훑어보니 기억이 도움이 된다. 이번 시간에는 만들었던 감성일기장을 최적화하고 가다듬어서 배포해서 이 강좌를 마무리하는 것을 목표로 마지막 한입크기 리액트 포스팅을 해보겠다. 아자아자!!!
이슈 1. 레이아웃이 내가 의도한대로 가로배열이 안됐다.
이슈 2. 일기 작성 시 감정을 선택하면 무엇을 선택했는지 알 수가 없다.
.EmotionItem {
cursor: pointer;
border-radius: 5px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
App.css에 위와 같은 클래스를 만들어서 넣어줬다.
여기서 flex-direction을 column으로 지정한 것이 처음에는 이해가 안됐다. 세로로 정렬돼있는 저 셀렉터들을 가로로 나열하고 싶은건데 왜 column으로 넣으라는건지 이해가 안됐지만 이번에도 역시나 내가 멍청했다,,
감정img와 아래에 '완전좋음'을 세로로 유지하기 위해서 플렉스 방향을 column으로 뒀다.
.DiaryEditor .emotion_list_wrapper {
display: grid;
grid-template-columns: repeat(5, auto);
gap: 2%;
}
그리고 grid 레이아웃을 사용해서 원하던 가로 정렬을 구현할거다. 노마드코더 코코아톡 챌린지 때부터 flexbox는 익숙하게 다뤄와서 익숙했는데 그리드는 처음 써봤다. 근데 뭔가 훨씬 직관적이고 익숙해지기만 하면 더 편할 것 같은 느낌..?
gird-template-columns: repeat(5, auto); 를 보자.
가로로 그리드 다섯개를 정렬할거고, 다섯개의 열을 나열하고 그 아이템들의 사이즈는 자동으로 조정해라! 그리고 아이템과 아이템 사이의 gap은 2%로 하겠다! 라는 뜻이다.
그리고 이슈 1번을 해결하기 위해서, 내가 선택한 감정이 뭔지 알 수 있어야 하기 때문에 EmotionItem.js파일을 만들어서 prop을 이용해서 내가 선택한 아이템에는 true를, 선택받지 않은 녀석들은 false로 넣어두게끔 해두자. 아래에 js, css둘다 코드 첨부하겠음.
import React from "react";
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_descript,
onClick,
isSelected,
}) => {
return (
<div
onClick={() => onClick(emotion_id)}
className={[
"EmotionItem",
isSelected ? `EmotionItem_on_${emotion_id}` : `EmotionItem_off`,
].join(" ")}
>
<img src={emotion_img} />
<span>{emotion_descript}</span>
</div>
);
};
export default React.memo(EmotionItem);
이렇게 리액트 만들어놓고 내가 선택한 감정은 그 아이템 박스 전체가 그 감정색깔과 똑같이 채워지게끔 만들어보자.
.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;
}
짜라란 ~ 내가 만지는 것들이 하나씩 바로바로 눈에 보이니까 좀 재미가 붙는듯하다.
일단 Edit.js파일을 아래와 같이 완성했당.
import { useState, useContext, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { DiaryStateContext } from "../App";
import DiaryEditor from "../components/DiaryEditor";
const Edit = () => {
const [originData, setOriginData] = useState();
const navigate = useNavigate();
const { id } = useParams();
const diaryList = useContext(DiaryStateContext);
useEffect(() => {
const titleElement = document.getElementsByTagName("title")[0];
titleElement.innerHTML = `감정 일기장 - ${id}번 일기 수정`;
}, [id]);
useEffect(() => {
if (diaryList.length >= 1) {
const targetDiary = diaryList.find(
(it) => parseInt(it.id) === parseInt(id)
);
if (targetDiary) {
setOriginData(targetDiary);
} else {
alert("없는 일기입니다.");
navigate("/", { replace: true });
}
}
}, [id, diaryList, navigate]);
return (
<div>
{originData && <DiaryEditor isEdit={true} originData={originData} />}
</div>
);
};
export default Edit;
솔직하게 여기 완벽히는 이해 안 된다.
DiaryList에서 useParams()로 받아온 저 {id}라는 일기 데이터 값을 하나씩 꺼내오는 원리이다. useEffect()를 써서 가져올거고, id나 diaryList가 변했을 때마다 다른 데이터 값을 꺼내와야 하기 때문에 이 부분도 같이 처리해준다. edit 컴포넌트가 마운트 됐을 때 동작한다는 거 정도 기억 하고 넘어갈래...
const targetDiary = diaryList.find(
(it) => parseInt(it.id) === parseInt(id)
그리고 이 부분. 내가 꺼내온 id의 data값이 문자열인지 숫자형인지 알 수 없는데 만약 다르다면 충돌이 발생할 수도 있기 때문에 아싸리 둘 다 parstInt씌워서 정수로만 만들어뒀다.
자 이 부분에서 날짜를 내가 이 일기장을 보고 있는 그 날을 디폴트로 미리 잡아두기 위해서 다음과 같이 코드를 추가했다.
useEffect(() => {
if (isEdit) {
setDate(getStringDate(new Date(parseInt(originData.date))));
setEmotion(originData.emotion);
setContent(originData.content);
}
}, [isEdit, originData]);
이렇게 추가해주면 의도한대로 된다. 그리고 지금 가다듬고 있는 '수정하기'페이지에서도 적용돼서 내가 과거의 일기를 수정하면 과거의 일기가 아닌 현재의 날짜를 기준으로 다시 날짜 정보도 업데이트 할 수 있게 된다.
자 이제 진짜 마지막 배포 단계!
이렇게 npm run build 명령어를 입력해주면 뭐라뭐라 하다가
이렇게 빌드를 위한 폴더가 준비되었다는 문구가 뜬다. 규모가 크지 않았는데도 나는 40초 정도는 기다렸던 것 같다. 프로젝트의 규모가 클수록 이 과정의 시간이 더 오래 소모된다고 한다.
그러면 이제
이렇게 npm install -g serve 명령해주고
마지막으로 serve -s build를 명령해주면 !?!?!?
젠장 이게 뭔 ,,, ?
serve : 이 시스템에서 스크립트를 실행할 수 없으므로 C:\Users\iank1\AppData\Roaming\npm\serve.ps1 파일을 로드할 수 없습니다. 자세한 내용은 about_Execution_Policies(https://go.microsoft.com/fwlink/?LinkID=135170)를
참조하십시오.
위치 줄:1 문자:1
- serve -s build
+ CategoryInfo : 보안 오류: (:) [], PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess
라는 에러가 떠서 포기하고 자고나서 하려고 했지만
우리 희건이가 해결해줬다. 저대로 하고 Y/N에서 Y만 입력하고나서 다시 터미널로 와서 실행했더니 잘 된다.
참고로 ctrl+c를 누르면 shut down이 된다. 얘는 코드를 수정하고 저장한 다음 localhost3000을 새로고침 한다고 변경사항이 적용되지 않는다. 변경된 사항이 있다면 빌드를 셧다운하고 다시 빌드해서 배포해야 한다는 점 기억하장.
Firebase를 사용해서 찐배포를 시작해보자 !
둥둥...둥둥.....둥둥두웅....
cmd를 관리자 권한으로 실행해서
이렇게 firebase에서 알려준 명령어를 치면 한참동안 뭔가가 설치가 된다.
...
혹시나 따라가다가 하나라도 놓치면 꼬일까봐 조마조마 하면서 윈터루드 형의 가이드를 따라서 배포까지 성공했다 !!
(https://ian-react-project-emotiondiary.web.app/)
이 주소로 들어가면 내가 열심히 만든 갬성 일기장이 등장한다!
자 이제 진짜 찐막! 오픈그래프 설정만 하면 된다.
이랬던걸
이렇게까지는 만들었는데 이미지가 안들어간다 !!
문제는 역시 경로였다. 썸네일로 띄우고 싶은 이미지를 public폴더에 넣었어야 했는데 그거보다 하나 하위폴더에 넣어놔서 안됐던 것이다.
왜냐하면
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="나만의 감정 일기장" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<meta property="og:image" content="%PUBLIC_URL%/thumbnail.png" />
<meta property="og:site_name" content="갬성 일기장" />
<meta property="og:description" content="나 김이안이 직접 만든 갬성 일기장" />
여기서 보이는 것 처럼 %퍼블릭 URL을 쓰고 있는데 저 퍼센트는 경로를 public으로 보내서 그 안에 있는 다음 ~~파일을 읽어오겠다는 뜻이기 때문! 이미지 파일의 위치만 옮겼다고 하더라도 엄연한 코드 수정이니까 다시 빌드해서 배포까지 해보자.
npm run build
firebase deploy
지겹도록 치고 있는 두 명령어 ....
하하~ 완성했다.
일단 리액트! 만만하지 않았다. 함께 반학기 동안 함께 스터디한 우리 TAB 웹 스터디 팀(한림,지민,이산드라,서연,희건,성준 그리고 혜진) 덕분에 김 빠지지 않고 꾸준히 열정적으로 따라올 수 있었던 것 같다. 어려웠고 솔직히 따라서 만든 지금도 다시 혼자 만들라면 절대 못 만들 것 같지만 리액트가 어떤 원리와 기능들을 기반으로 동작하는지, 어떻게 써먹을 수 있는지 또 내가 만든 웹을 어떻게 배포할 수 있고 어떻게 수정할 수 있는지, 링크 썸네일(OG)도 어떻게 수정하고 만질 수 있는지는 명확하게 이해하고 배울 수 있는 시간이었다.
다른 분야에 대한 공부보다는 한 김에 리액트를 조금 더 공부해보고 싶고 이거로 프로젝트를 하나 더 해보고 싶다는 생각도 든다. 아무튼 뿌듯하다 ! 이상.