투두리스트 프로젝트를 하면서 느꼈던 점들과 배운 것들, 어려웠던 점들을 정리해본다.
배포 링크 : https://ansmeer008.github.io/TODO-LIST-APP/
혼자 해보는 프로젝트였지만 코드스테이츠에서 정해준 가이드라인이 있었다면 'CRUD'가 동작하는 Todo앱을 만들라는 것이었다.
내가 만든 앱에서 CRUD가 동작하는 방식은 아래와 같다.
C : 새로운 할 일 생성, 할 일 완료시 쿠키 생성
R : 할 일 데이터 불러오기, 쿠키 데이터 불러오기
U : 체크 박스를 이용해 할 일을 완료했을 때 체크된 상태로 수정하기
D : 할 일, 쿠키 삭제
/
├── node_modules
├── public
│ └── index.html
│
├── src
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── InputBox.js
│ │ ├── Nav.js
│ │ ├── TodoContent.js
│ │
│ ├── pages
│ │ ├── Cookies.js
│ │ ├── Information.js
│ │ ├── Menu.js
│ │ └── ToDoCalendat.js
│ │ └── TodoListContainer.js
│ │
│ ├── App.js
│ └── index.js
│ └── App.css
│
├ .gitignore
├ package-lock.json
├ package.json
└ README.md
처음 구현할 때도 힘들었고, 리덕스로 리팩토링 할 때도 체크박스 상태를 변경시켜주는 것이 정말 힘들었다. 체크박스는 투 두 목록들 각각이 가지고 있는 것이므로 ToDoContent라는 컴포넌트에 포함된 요소였고, dummy data를 사용할 때는 아래 예시처럼 status라는 키의 값으로 "active","completed"와 같이 문자열을 사용하다가 redux를 사용할 때 boolean으로 바꿔서 사용하기로 했다.
//더미 데이터 예시
const data = [{
id: 1,
content: "장보러 가기",
date: new Date(),
status: "active",
},...]
//Redux Refactoring 후 TodoContent 컴포넌트
export default function TodoContent({ tododata }) {
const { id, content, status } = tododata;
const dispatch = useDispatch();
const handleChange = () => {
dispatch(setCheckboxStatus(id));
};
const handleDelete = () => {
dispatch(deleteToDo(id));
};
return (
<div className="content-container">
<input
className="content-checkbox"
type="checkbox"
id={id}
checked={status === true}
onChange={() => handleChange()}
></input>
<label htmlFor={id} className="content-text">
{content}
</label>
<button className="content-delete-button" onClick={() => handleDelete()}>
<TbTrash />
</button>
</div>
);
}
참고로 체크박스 상태를 변경하는 케이스를 포함하는 todoReducer는 아래와 같이 작성되었다.
import { ADD_TODO, DELETE_TODO, SET_CHECKBOX_STATUS } from "../actions/index";
import { initialState } from "./initialState";
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
todo: [
...state.todo,
{
id: action.payload.itemId,
content: action.payload.todoContent,
date: new Date(),
status: false,
},
],
});
break;
case DELETE_TODO:
return Object.assign({}, state, {
todo: state.todo.filter((el) => el.id !== action.payload.itemId),
});
break;
case SET_CHECKBOX_STATUS:
let idx = state.todo.findIndex((el) => el.id === action.payload.itemId);
return Object.assign({}, state, {
todo: [
...state.todo.map((el, index) =>
index === idx
? {
...el,
status: !el.status,
}
: el
),
],
});
break;
default:
return state;
}
};
export default todoReducer;
역시 기술은 안 써먹으면 까먹는다고... Redux도 정리해둔 내용을 다시 읽다시피 했다. 한 번 써봤던 거라 익숙하지 않은 부분이 아~예 없다곤 할 수 없겠지만서도, 자주 사용하지 않고 자주 보지 않으면 막막함을 느낄 수밖에 없는 것 같다.
어렵게 느껴졌던 부분은 아무래도 이제 액션들을 정의할 때였는데, 단순히 add, delete만 정의할 것이 아니라 위에서 구현했던 체크박스의 상태를 변경시킬 SET_CHECKBOX_STATUS도 필요했다. 그에 따라서 어떤 것들을 파라미터로 받아와야 이후에 리듀서에서 가공이 가능할지 하나하나 생각하는 과정이 쉽지 않았다.
//action/index.js 파일
export const ADD_TODO = "ADD_TODO";
export const DELETE_TODO = "DELETE_TODO";
export const SET_CHECKBOX_STATUS = "SET_CHECKBOX_STATUS";
export const ADD_COOKIE = "ADD_COOKIE";
export const DELETE_COOKIE = "DELETE_COOKIE";
//todo에 새로운 할 일이 추가되는 행동 (inputbox, todocontent 등에 필요할듯)
export const addToDo = (itemId, todoContent) => {
return {
type: ADD_TODO,
payload: {
itemId,
todoContent,
},
};
};
//todo에서 할 일을 삭제하는 행동
export const deleteToDo = (itemId) => {
return {
type: DELETE_TODO,
payload: {
itemId,
},
};
};
//todo 속 status 속성을 변경하는 행동
export const setCheckboxStatus = (itemId) => {
return {
type: SET_CHECKBOX_STATUS,
payload: {
itemId,
},
};
};
//cookies에 새 쿠키를 추가하는 행동
export const addCookie = (itemId, cookieIcon, cookieDate) => {
return {
type: ADD_COOKIE,
payload: {
itemId,
cookieIcon,
cookieDate,
},
};
};
//cookies에서 해당 id를 가진 쿠키를 제거하는 행동
export const deleteCookie = (itemId) => {
return {
type: DELETE_COOKIE,
payload: {
itemId,
},
};
};
로컬 스토리지를 겨우 구현했는데, 막상 리덕스로 리팩토링을 하려니 리덕스에서 로컬 스토리지를 사용할 수 있는지를 몰랐다. 구글링을 해보니 Redux-persist라는 것을 사용하면 된다고 해서 여기를 참고해서 진행하니 정말 마법처럼 ㅎㅎ 로컬 스토리지가 구현되었다. 사실 마법이라고 표현한 것처럼 이 툴에 대해서 잘 알지 못하기 때문에 지금은 별 문제 없이 잘 사용했지만, 다음에 다른 프로젝트에서 사용할 때에는 에러가 발생하거나, 잘 안 맞을 수 있다는 생각도 든다. 뭔가 잘 알지도 못하는 툴을 사용한다는 점이 잘했다고 할 수는 없을 것 같다.
기능 구현에 힘쓴다고 CSS에는 신경을 못 쓰다 보니 App.css에 몰아서 css를 작성하게 되었는데, 그 과정에서 선택자들이 많아지는 것을 보니... 왜 postcss나 다른 라이브러리를 사용하는지 알 거 같았다. 코드가...많이 더러워졌다.
오타 때문에 시간을 버린 게 한두 번이 아니다... prettier가 있어서 그나마 이정도겠지..? 오늘 리팩토링 할 때는 사실 단순 오타라기 보다는 키 값을 잘못 전달해줬었는데, 그것때문에 정말 내가 똥멍청이라서 해결을 못하는 건가...하면서 좌절하기도 했다. 발견하고 난 뒤의 허무함이란...ㅎㅎ...그래도 해결되어서 다행이라며 웃을 수 있었지만... 이런 오타를 줄일 수 있는 방법을 정말정말 고민해봐야겠다...
어제와 오늘 고난의 흔적을 보시라...
마지막 리덕스 리팩토링을 마무리하고 난 뒤에 이제 배포 링크만 만들면 된다~~ 룰루랄라 하던 나에게 찾아온 것은 수많은 에러들이었다...
분명 그냥 전에 했던 대로 내가 블로그에 써뒀던 글과 노션에 적어뒀던 내용들을 기반으로! 동일하게! 번들링과 배포를 진행한 것 같은데 내 깃허브 페이지 링크는 아래 이미지와 같은 에러들을 콘솔창에 띄운 빈 ! 화 ! 면 ! 만 출력했다.
해결하려고 했던 시도들
npm run build를 통해 번들링하고 (pakage.json 파일의 scripts 부분 설정을"build": "BUILD_PATH='./docs' react-scripts build"로 해줌),
해당 내용을 깃허브에 push 한 뒤, setting => github pages에서 docs 폴더 선택해주기.
netlify에서 배포해보기
Router에 base를 지정해줘야 한다고 해서 .env 파일을 만들고 PUBLIC_URL을 만들어 과 같은 형식으로 지정해주기.
babel.config.js 파일 생성하기
BrowserRouter를 HashRouter로 바꾸기
결론적으로 5번 방법으로 배포에 성공하게 되었다.
gh-pages 에서 배포 할 때 react-router-dom 의 BrowserRouter 을 쓰면 화면이 안나오고 에러가 나올 수가 있다고 한다. 그래서 HashRouter를 권장한다고 하는데, HashRouter을 사용하면 URL 에 #이 붙게 된다고 한다.
(사실 HashRouter를 쓰는 대신에 3번을 해줘도된다는 것 같은데...내가 했을 때는 안 됐다... 아마 내가 뭔가를 잘못해서 그런거겠지만...)
두 라우터의 차이점을 찾아보니 아래와 같았다.
- 해쉬 라우터는 정적 페이지에 적합, 브라우저 라우터는 동적 페이지에 적합
- 해쉬 라우터는 검색 엔진이 읽지 못하는데, '#' 때문에 서버가 페이지의 유무를 알 수 없다. 그래서 거의 사용하지 않는 것이기도 함.
배포가 계속 실패해서 어제부터 오늘까지 하루종일 뭐가 걸린 것처럼 답답하고 침울했는데... 그래도 결국 해결하고 나니 기분이 정말...상쾌하다.
그래도 이 모든 게 내 첫 솔로 프로젝트라고 생각하면 견딜만 했다. 처음부터 잘 하는 사람이 어딨냐고 생각하면 또 좀 할 만 했다. 물론 괴물 같은 사람들이 너무 많은 분야라는 건 알지만 알게 뭐람 ~~ 애초에 그런 사람과 나를 비교하는 건 바보 같은 일이라는 걸 잘 안다. 이 프로젝트는 내 시작이고, 출발점이라는 점을 잘 되새기며 이렇게 내 첫 프로젝트를 마무리해보려고 한다.