Chromium 156.0.8078.74. 본문의 HTML·CSS·JavaScript를 실제 브라우저로 렌더링하고 현재 화면을 캡처했습니다. 모든 조작·레이아웃·접근성을 검증한 것은 아닙니다.
이 글의 순서 2개 단락
직접 고쳐서 실행해보기이 글의 예제로 바로 실습해요.실습 열기
입력값 input() · prompt()에 한 줄씩 전달
준비할 테이블과 데이터 매 실행 전 새로 준비해요.
실행하기를 눌러 확인해보세요.
예상 결과 보기
실행 환경에 따라 오류 문구나 표시 형식은 다를 수 있어요.
브라우저 JavaScript 할 일 목록 코스에서 입력값을 배열에 넣고 화면에 그리는 방법을 익혔다면, 이 노트에서 새로고침 후에도 목록을 복원하는 방법을 이어서 살펴보세요. 앞 강의의 파일이 없어도 아래 예제 하나로 실행할 수 있습니다.
새로고침하면 JavaScript가 처음부터 다시 실행됩니다. 이때 let tasks = []로 시작하고 저장된 값을 읽어 오지 않으면, 저장소에 데이터가 남아 있더라도 화면에는 빈 배열이 그려집니다. 반대로 배열을 복원해도 추가나 삭제 후 다시 저장하지 않으면 다음 새로고침에 이전 목록이 나타날 수 있습니다.
시작할 때 읽고, 변경할 때 저장하기
이 예제는 문자열 배열을 사용합니다. localStorage에는 문자열을 저장하므로 JSON.stringify(tasks)로 배열을 JSON 문자열로 바꾸고, 읽을 때는 JSON.parse()로 다시 배열로 바꿉니다. 저장된 키가 없으면 getItem()은 null을 반환하므로 빈 배열로 시작합니다. JSON 문법이 잘못됐거나 결과가 문자열 배열이 아니면 안내 메시지를 표시하고 빈 목록으로 시작합니다.
아래 전체 코드를 index.html로 저장해 실행하세요. 페이지의 스크립트가 목록 요소를 찾을 수 있도록 <script>를 화면 요소 뒤에 두었습니다. 시작 시 loadTasks()로 복원한 다음 renderTasks()로 첫 화면을 그립니다. 추가와 삭제 뒤에는 배열을 저장하고 다시 그립니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>할 일 목록 저장하기</title>
</head>
<body>
<main>
<h1>할 일 목록</h1>
<form id="task-form">
<label for="task-input">할 일</label>
<input id="task-input" autocomplete="off" required>
<button type="submit">추가</button>
</form>
<p id="message" role="status" aria-live="polite"></p>
<ul id="task-list"></ul>
</main>
<script>
const STORAGE_KEY = 'task-list-example-v1';
const form = document.querySelector('#task-form');
const input = document.querySelector('#task-input');
const list = document.querySelector('#task-list');
const message = document.querySelector('#message');
function loadTasks() {
let saved;
try {
saved = localStorage.getItem(STORAGE_KEY);
} catch (error) {
message.textContent = '저장소를 읽을 수 없어 빈 목록으로 시작합니다.';
return [];
}
if (saved === null) return [];
try {
const parsed = JSON.parse(saved);
if (!Array.isArray(parsed) || !parsed.every(item => typeof item === 'string')) {
throw new Error('문자열 배열이 아닙니다.');
}
return parsed;
} catch (error) {
message.textContent = '저장된 목록을 읽을 수 없어 빈 목록으로 시작합니다. 항목을 변경하면 기존 저장값을 덮어씁니다.';
return [];
}
}
function saveTasks() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
message.textContent = '';
} catch (error) {
message.textContent = '저장하지 못했습니다. 현재 화면의 변경 사항은 새로고침 후 사라질 수 있습니다.';
}
}
function renderTasks() {
list.replaceChildren();
tasks.forEach((task, index) => {
const item = document.createElement('li');
const label = document.createElement('span');
const button = document.createElement('button');
label.textContent = task;
button.type = 'button';
button.textContent = '삭제';
button.setAttribute('aria-label', `${task} 삭제`);
button.addEventListener('click', () => {
tasks.splice(index, 1);
saveTasks();
renderTasks();
});
item.append(label, ' ', button);
list.append(item);
});
}
let tasks = loadTasks();
renderTasks();
form.addEventListener('submit', event => {
event.preventDefault();
const task = input.value.trim();
if (!task) return;
tasks.push(task);
saveTasks();
renderTasks();
input.value = '';
input.focus();
});
</script>
</body>
</html>

renderTasks()는 현재 배열만 화면에 반영합니다. 저장은 saveTasks()가 담당하며, 삭제 버튼은 해당 항목을 배열에서 제거한 뒤 저장하고 목록을 다시 그립니다. 입력한 글은 textContent로 넣기 때문에 HTML 태그로 해석되지 않습니다.
직접 확인하기
다음은 코드가 의도한 동작입니다. 이 글에서는 브라우저에서 실제 클릭과 새로고침을 실행해 확인하지 않았습니다.
- 할 일을 두 개 추가하고 새로고침합니다. 저장소를 사용할 수 있다면 두 항목이 다시 나타납니다.
- 한 항목을 삭제하고 새로고침합니다. 남은 항목만 다시 나타납니다.
- 빈 저장소에서는 오류 없이 빈 목록으로 시작합니다. 손상된 JSON이나 문자열 배열이 아닌 값이 저장돼 있으면 안내를 표시하고 빈 목록으로 시작합니다. 이때 원래 값은 바로 지우지 않지만, 새 항목을 추가하는 등 저장에 성공하면 새 배열로 덮어씁니다.
브라우저 개발자 도구의 저장소 화면에서 task-list-example-v1 키를 살펴보면 저장된 JSON 문자열을 확인할 수 있습니다. 테스트를 마친 뒤에는 그 키만 삭제할 수 있습니다.
localStorage는 출처별로 구분되므로 주소의 프로토콜, 호스트 또는 포트가 달라지면 다른 저장소를 볼 수 있습니다. 브라우저 설정이나 실행 환경에 따라 접근 또는 저장이 제한될 수도 있습니다. 파일을 직접 여는 file: 주소의 저장 동작은 브라우저마다 다를 수 있으므로, 복원이 불안정하면 같은 로컬 웹 서버 주소에서 다시 확인하세요. 저장에 실패한 경우에는 현재 화면을 계속 사용할 수 있지만 변경 내용이 유지된다고 보장할 수 없습니다.
참고: MDN — Storage.setItem(), MDN — localStorage, MDN — JSON 파싱 오류.
함께 찾아볼 코드 사전
CHECK THE SOURCE
참고한 공식 문서
기능 설명은 위 문서를 참고했으며, 예제의 입력·결과와 한계는 본문에 따로 기록했습니다.