바닐라 자바스크립트로 투두리스트를 만들어보고 있다.
하나씩 구현을 하다 보니 index.js에 너무 여러 가지의 함수가 혼재하게 되었고, 정리를 해야겠다 싶어 코드를 기능 단위로 파일을 만들어 분리를 해야겠다 싶었다. 그렇게 기존 index.js에 작성되어 있던 이벤트 핸들러 함수들을 handle.js 파일에 하나씩 이동시키기 시작했는데, 그러던 중 마주한 에러 메시지.
Uncaught TypeError: Assignment to constant variable
오류가 난 곳을 찾아가보니 함수 기능 분리를 위해 만든 handle.js 파일 내에 필터링한 todos 배열을 기존 todos 배열에 재할당하며 업데이트를 하는 코드에 문제가 있었다.
// handle.js
todos = todos.filter((todo) => todo.id !== id);
이 todos 배열은 index.js에 선언이 되어 있는 배열로, handle.js에서는 import를 해와서 사용하고 있었다.
// index.js
export let todos = [];
이상하다. 이 todos 배열은 handle.js에서 재할당이 이루어질 것을 고려해서 let 키워드를 사용해 선언을 했던 것이다. 그런데 const로 선언한 변수에 재할당을 했을 때나 나타날 법한 Uncaught TypeError: Assignment to constant variable 에러가 나고 있는 게, 나로서는 이해가 가지 않는 부분이었다.
문제 원인: 모듈에서 import한 변수였기 때문
문제의 원인은 모듈 시스템에 있었다. 자바스크립트의 모듈 시스템은 import해서 가져온 변수를 읽기 전용(read-only)으로 취급하기 때문에 let으로 선언된 변수라도 모듈에서 import한 변수는 재할당이 불가능하다.
왜 그런가 하니, import된 변수는 모듈 간의 상태를 공유하기 위해 만들어진 것이기 때문에, 재할당이 허용되면 모듈 간의 일관성이 깨질 위험이 있기 때문이라고 한다. 따라서 import로 가져온 변수는 재할당은 할 수 없다.
단, 내부 수정은 가능하다. 즉, todos 자체는 재할당이 불가능하지만 배열이기 때문에 참조된 메모리 내부의 데이터는 수정 가능하다. 예를 들어, 아래와 같이 todos.push() 작업은 허용되지만, todos = [] 같은 재할당은 불가능한 것이다.
import { todos } from './index.js';
todos.push({ id: 1, todo: "JS 공부" }); // ✅ 정상 동작
todos = []; // ❌ TypeError: Assignment to constant variable
문제 해결: 상태 관리 함수 만들기
그렇다면 문제를 어떻게 해결을 해야 할까 찾아보다 Stackoverflow의 글을 보고 힌트를 얻었다.
질문 작성자도 나와 같은 문제를 겪고 있었는데, 이 문제를 해결하기 위해선 해당 변수를 가지고 있는 모듈 내에서 함수를 정의한 후 그것을 export해서 사용하면 된다고 한다.
그래서 나도 이 방식을 적용하기로 했고, 이렇게 하는 김에 아예 crud.js라는 파일을 하나 만들어서 문제가 됐던 삭제 부분의 처리뿐만 아니라 todos 배열에 대한 다른 부분의 처리를 담당하는 함수들도 한데 모아 상태 관리를 할 수 있도록 만들기로 했다.
// crud.js
export let todos = [];
export const setTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos));
};
export const getTodos = () => {
const loadedTodos = JSON.parse(localStorage.getItem('todos'));
if (!loadedTodos) return;
return loadedTodos;
};
export const addTodo = (item) => {
todos.push(item);
};
export const editTodo = (id, newTodo) => {
const thisTodo = todos.find((todo) => todo.id === id);
thisTodo.todo = newTodo;
};
export const deleteTodo = (id) => {
todos = todos.filter((todo) => todo.id !== id); // 문제가 됐던 부분
};
이렇게 todos 배열을 직접 다루는 함수들을 todos 배열과 한 곳에 모아두고 필요한 곳에서 사용하니 더 이상 재할당 에러가 나오지 않았다!
문제가 해결된 것 뿐만 아니라 상태를 관리하는 함수들을 한 곳에 모아둠으로써 명확하게 지저분하게 펼쳐져 있던 코드들이 기능에 맞추어 정리가 되는 효과도 볼 수 있었다.
추후에 이 crud.js 안의 함수는 json-server을 이용해 http 통신을 구현해 볼 예정이고, 그 후엔 express를 활용해 간단한 백엔드도 구현을 해 볼 생각이다. 그리고 상태 관리 라이브러리도 공부해 볼 겸 도입을 해보려 한다!
참고
'Frontend > HTML ∙ CSS ∙ JavaScript' 카테고리의 다른 글
[JS] net::ERR_ABORTED 404 오류 해결 (1) | 2024.11.29 |
---|---|
[JS] Uncaught SyntaxError: Cannot use import statement outside a module 에러 해결 (0) | 2024.11.29 |
[JS] toLocaleString()를 사용하여 날짜를 원하는 포맷의 문자열로 저장하기 (0) | 2024.11.29 |
[JS] crypto.randomUUID()를 사용하여 자바스크립트로 랜덤 id 생성하기 (2) | 2024.11.29 |
[JavaScript] 얕은 복사와 깊은 복사 (0) | 2023.11.06 |