오늘 정오로 펜레터 프로젝트가 끝났다. 어렵진 않았지만 낯설었고 많이 배울 수 있는 기회였다. 오늘은 좀 쉬면서 며칠간 쓴 코드를 다시 보고, 자잘한 문제를 수정하고 배포를 했다. 오늘 포스팅에서는 이번 개인 프로젝트를 진행한 과정과 진행하며 배운 것들을 간략하게(강조) 정리해보려고 한다.
프로젝트 시작이 늦은 편이었다. 특별히 게으름을 피운 건 아닌데 알고리즘 스터디에 뭐에 이것저것 하다보니 어느덧 마감기한을 사흘 앞두고 있었다. 조금 초조했지만 다른 캠퍼들의 고충과 시행착오를 들어 둔 덕택에(?) 전체적인 프로젝트의 레이아웃이 머릿속에 그려져 있었다.
머릿속에 이런 구조를 이루고 있었고, 이 구조에 수정 없이 그대로 프로젝트를 마쳤다.
(context API, Redux 적용 제외)
📦public
┗ 📜fakedata.json
📦src
┣ 📂assets
┣ 📂components
┃ ┣ 📜Footer.jsx
┃ ┣ 📜Header.jsx
┃ ┣ 📜Letter.jsx
┃ ┣ 📜LetterForm.jsx
┃ ┗ 📜LetterList.jsx
┣ 📂pages
┃ ┣ 📜Detail.jsx
┃ ┗ 📜Home.jsx
┣ 📜App.js
┗ 📜index.js
const [letters, setLetters] = useState({});
const [selectedMember, setSelectedMember] = useState("");
이렇게 state
도 어느 정도 정해두고 프로젝트에 착수했다.
컴포넌트의 주요 기능을 순조롭게 개발해가고 있던 도중, 두 가지 부분에서 막히고 말았다.
프로젝트 요구사항에 json 포멧의 목데이터를 이용해서 내용을 채워야하는 부분이 있었다. 이를 위해 렌더링시에 useEffect
hook 을 이용해서 localStorage에 데이터가 없을 시 목데이터를 가져오도록 코드를 짰다.
useEffect(() => {
if (!localStorage.getItem("letters")) {
fetch("../public/fakeData.json")
.then((res) => res.json())
.then((data) =>
data.forEach((item) => {
setLetters((prevState) => ({
...prevState,
[item.id]: { ...item, editedAt: "" },
}));
})
);
const stringifiedLetterMap = JSON.stringify(letters);
localStorage.setItem("letters", stringifiedLetterMap);
} else {
const storageData = JSON.parse(localStorage.getItem("letters"));
setLetters(storageData);
}
}, []);
그런데 계속해서 데이터를 가져오지 못하는 것이었다.
fetch
또한 맞게 쓰이고 있는데계속해서 파일을 찾지 못하는 것이었다. 여기에서 몇 시간을 날린 건지... 결국 두 손 두 발 다 들고 다른 캠퍼한테 물어봤더니 본인도 같은 문제를 겪었고 그래서 리액트의 작동 방식을 뜯어본 결과
프로젝트 빌드 전과 빌드 후의 경로 구성이 vscode의 탐색기에서 보는 것과 차이가 있는 것 같다는 것이다 (!)
정확히 규명을 해보지는 못했지만 리액트는 빌드를 하면 babel
과 webpack
을 사용해서 소스코드를 찢고 붙이고 다시 꿰매서 빌드용 js 파일을 만들고, 이걸 바탕으로 앱을 빌드한다.
그래서 그냥 루트 디렉토리에서 바로 접근하게끔 fetch("fakeData.json")
으로 경로를 잡았더니 잘 불러오는 것이었다... 여기서 한 쪽 무릎을 꿇었다. 깨달은 건... CRA를 사용해서 앱을 만들다가 경로가 안 잡히면 일단 루트 디렉토리로 한번 잡아보자. 정도였다.
순조롭게 props-drilling 버전과 context API 적용 버전을 끝내고 redux 적용 버전을 작성하고 있었는데, 리듀서에서 각각의 액션 타입에 대한 리턴값을 정의하다가 여기서 또 한차례 막히고 말았다.
원래 존재하는 객체에 수정한 객체의 내용을 덮어씌우는 작업이었는데, 나는 공연히 어렵게 어렵게 풀어서 작성한 것이다.
// React Component
dispatch(
editLetters({
[params.id]: {
...selectedLetter,
content: updatedContent,
editedAt: dayjs().toJSON(),
},
id: params.id,
})
);
// 리듀서
case EDIT_LETTER:
return {
...state,
letters: {
...state.letters,
[action.payload.id]: {
...state.letters[action.payload.id],
content: action.payload.updatedContent,
editedAt: action.payload.editedAt
}
},
};
이렇게 작성한 EDIT_LETTER action을 dispatch 하는 부분에서 계속 에러가 발생하는 것이다. 에러가 안 나면 오히려 이상할 생김새긴 했는데... 전날 밤을 새워서 그런지 머리도 어질어질하고 원래 구조분해할당에는 조금 자신이 없는 편이기도 해서 어떻게 할 지 몰라 한참을 갈팡질팡 하다가 다른 캠퍼분에게 질문을 했다. 이 캠퍼분의 해결 방법이 정말 좋았는데, 이렇게 써 둔 걸 실제로 임의의 데이터를 하나 가정해서 대입을 해 보는 것이다. 그렇게 해 보고 나니 간단히 문제를 알 수 있었고, 다음과 같이 리듀서를 수정했다.
case EDIT_LETTER:
return {
...state,
letters: {
...state.letters,
...action.payload,
},
};
사실 이렇게만 해도 원래 존재하는 id
에 덮어씌워지기 때문에 수정이 완료되는 것이다. 구조분해와 객체의 개념을 다시 공부해야 할 것 같다. 그리고 이 자리를 빌려 다시 지노님에게 감사를 전한다...
물론 막힌 부분은 있었지만 그건 프로젝트 자체의 난이도라기보다 내가 선택한 방식과 어쩔 수 없이 겪어야 하는 어려움들로 인한 것이었다. 프로젝트 전체적으로 보면 React hook을 실습하는 것에 가까웠고, 그 결과 이전보다 hook들과 많이 친해질 수 있었다. 익힌 게 녹슬기 전에 어서 다음 프로젝트로 또 몸에 익히고 싶다.
야호!