이번 편의 결과물: 06편까지는 새로고침하면 사라지던 메모가 이제 localStorage에 저장되어 새로고침·재접속해도 유지됩니다. · 다루는 개념: localStorage.setItem/getItem, JSON 직렬화, 저장 실패(quota) 처리
이 편에서 만드는 파일
notes-app/
└── src/
├── storage.js (+ localStorage 저장/불러오기)
└── notes.js (~ 초기화 시 불러오기, 변경 시 저장하기)개념 정리
localStorage
localStorage는 문자열 키-값 쌍을 브라우저에 origin 단위로 저장합니다. 탭을 닫아도, 컴퓨터를 재부팅해도 사용자가 직접 지우기 전까지 남습니다.
| 메서드 | 역할 |
|---|---|
localStorage.setItem(key, value) | 문자열 값을 저장(값이 문자열이 아니면 자동으로 문자열 변환됨) |
localStorage.getItem(key) | 저장된 문자열을 반환, 없으면 null |
localStorage.removeItem(key) | 해당 키 삭제 |
값은 문자열만 저장할 수 있으므로, 배열이나 객체는 JSON.stringify로 문자열로 바꿔 저장하고 JSON.parse로 다시 객체로 되돌립니다.
저장 실패 대비
localStorage는 origin당 보통 5~10MB로 용량이 제한됩니다. 용량을 넘거나 브라우저가 저장을 차단(프라이빗 모드 등)하면 setItem이 예외를 던집니다. try/catch로 감싸지 않으면 이후 코드 전체가 멈춥니다.
try {
localStorage.setItem('notes-app:notes', JSON.stringify(notes));
} catch (error) {
console.error('메모를 저장하지 못했습니다.', error);
}JSON.parse도 저장된 값이 손상되어 있으면 예외를 던지므로 불러올 때도 같은 방식으로 감쌉니다.
실습
1. src/storage.js 만들기
src 폴더에 새 파일 storage.js를 만듭니다.
2. 코드 작성
// src/storage.js
const STORAGE_KEY = 'notes-app:notes';
export function loadNotes(fallback = []) {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return fallback;
return JSON.parse(raw);
} catch (error) {
console.error('메모를 불러오지 못했습니다.', error);
return fallback;
}
}
export function saveNotes(notes) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
return true;
} catch (error) {
console.error('메모를 저장하지 못했습니다.', error);
return false;
}
}src/notes.js를 열어 초기 배열을 기본값으로 두고, loadNotes로 저장된 값이 있으면 그것을 쓰도록 바꿉니다. 상태를 바꾸는 세 함수 끝에 saveNotes(notes) 호출을 추가합니다.
// src/notes.js
import { loadNotes, saveNotes } from './storage.js';
const defaultNotes = [
{
id: 'n1',
title: 'Vite 프로젝트 만들기',
body: 'npm create vite@latest notes-app -- --template vanilla 로 시작한다.',
tags: ['setup'],
pinned: true,
updatedAt: '2026-09-15T09:00:00+09:00',
},
{
id: 'n2',
title: '이벤트 위임 메모',
body: '목록 전체에 리스너 하나를 달고 event.target.closest 로 버튼을 찾는다.',
tags: ['dom', 'events'],
pinned: false,
updatedAt: '2026-09-15T11:20:00+09:00',
},
{
id: 'n3',
title: 'fetch 취소',
body: 'AbortController 의 signal 을 fetch 옵션으로 넘기고 abort() 로 취소한다.',
tags: ['fetch'],
pinned: false,
updatedAt: '2026-09-16T08:00:00+09:00',
},
{
id: 'n4',
title: 'IndexedDB 는 트랜잭션 단위',
body: 'objectStore 접근은 transaction 안에서만 가능하다.',
tags: ['storage'],
pinned: false,
updatedAt: '2026-09-16T09:30:00+09:00',
},
];
let notes = loadNotes(defaultNotes);
function generateId() {
return `n${Date.now()}`;
}
export function getNotes() {
return notes;
}
export function addNote({ id, title, body, tags = [], pinned = false, updatedAt }) {
const note = {
id: id ?? generateId(),
title,
body,
tags,
pinned,
updatedAt: updatedAt ?? new Date().toISOString(),
};
notes = [note, ...notes];
saveNotes(notes);
return note;
}
export function removeNote(id) {
notes = notes.filter((note) => note.id !== id);
saveNotes(notes);
}
export function updateNote(id, patch) {
notes = notes.map((note) =>
note.id === id ? { ...note, ...patch, updatedAt: new Date().toISOString() } : note,
);
saveNotes(notes);
}loadNotes(defaultNotes)는 저장된 값이 없을 때만 defaultNotes를 씁니다. 한 번이라도 addNote나 removeNote가 호출되면 그 이후부터는 localStorage에 저장된 값이 항상 우선합니다.
3. 실행
npm run dev확인
- 메모를 하나 추가한 뒤 브라우저를 새로고침하면 추가한 메모가 그대로 남아 있습니다.
- 브라우저 개발자 도구
Application탭 →Local Storage에서notes-app:notes키에 JSON 문자열이 저장된 것을 확인합니다. - 개발자 도구 콘솔에서
localStorage.removeItem('notes-app:notes')를 실행하고 새로고침하면 04편의 초기 데이터로 돌아갑니다.
직접 해보기
storage.js에clearNotes()함수를 추가하고,notes.js에서 이를 이용해 “전체 삭제” 버튼을 만들어 봅니다.saveNotes가false를 반환하면(저장 실패) 화면 상단에 “저장 공간이 부족합니다” 메시지를 잠깐 보여주도록main.js를 고쳐 봅니다.
정답 보기
// src/storage.js에 추가
export function clearNotes() {
localStorage.removeItem(STORAGE_KEY);
}// src/notes.js의 addNote 안
const saved = saveNotes(notes);
if (!saved) {
window.alert('저장 공간이 부족합니다.');
}자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 새로고침해도 계속 초기 데이터만 보임 | addNote 등에서 saveNotes(notes) 호출을 빠뜨림 | 상태를 바꾸는 함수 끝에 항상 saveNotes(notes)를 추가한다 |
JSON.parse 에러로 화면이 하얗게 뜸 | 저장된 값이 손상됐는데 try/catch 없이 파싱 | loadNotes 내부처럼 try/catch로 감싸고 실패 시 기본값을 반환한다 |
| 다른 탭에서 추가한 메모가 현재 탭에 안 보임 | localStorage는 자동으로 다른 탭에 반영되지 않고 새로고침해야 반영됨 | storage 이벤트를 듣거나(선택 심화), 우선 새로고침으로 확인한다 |