메모리 변수 todos에만 할 일을 담아 두면 브라우저를 새로고침(F5)하거나 탭을 닫았다 열었을 때 처음 상태로 돌아갑니다. 새로고침해도 데이터가 그대로 남게 하려면 브라우저의 작은 저장소인 **localStorage**에 **JSON.stringify**와 **JSON.parse**로 저장하고 꺼내야 합니다.
1저장하기: localStorage.setItem과 JSON.stringify
localStorage는 브라우저가 사이트마다 따로 마련해 둔 키·값 저장소입니다. 가장 중요한 특징은 오직 문자열(글자)만 저장할 수 있다는 점입니다.
따라서 객체 배열 todos를 저장할 때는 8단원 JSON: 객체와 글 사이에서 배운 **JSON.stringify(todos)**로 배열을 JSON 문자열로 바꾼 뒤 **localStorage.setItem(키, 값)**에 넘깁니다.
const todos = [
{ id: 1, text: '우유' },
];
const bad = String(todos);
const good =
JSON.stringify(todos);
console.log(bad);
console.log(good);
[object Object]
[{"id":1,"text":"우유"}]- 만약
JSON.stringify없이 배열을 그대로setItem('todos', todos)에 넣으면, 브라우저가 배열을 강제로 문자열로 바꿔'[object Object]'(또는 문자열 배열이면'우유 사기,산책하기')로 뭉개 버립니다. JSON.stringify(todos)로 바꿔 넣어야 대괄호와 중괄호 구조가 온전히 문자열로 보존됩니다.
// app.js — 브라우저에서 저장할 때
'use strict';
function save(todos) {
localStorage.setItem(
'todos',
JSON.stringify(todos),
);
}
2꺼내기: getItem의 null 처리와 JSON.parse
저장해 둔 문자열을 다시 꺼낼 때는 **localStorage.getItem('todos')**를 부릅니다. 이때 사용자가 페이지를 맨 처음 열어서 아직 저장한 적이 없다면 getItem은 **null**을 돌려줍니다.
그래서 널 병합 연산자(??)로 **raw ?? '[]'**를 만들어, null일 때는 빈 배열 글자 '[]'가 JSON.parse에 들어가게 합니다. 또한 저장소의 글자가 실수로 깨져 있을 때 앱이 멈추지 않도록 try / catch로 감쌉니다.
function parseTodos(raw) {
try {
return JSON.parse(
raw ?? '[]',
);
} catch {
return [];
}
}
이어서 세 가지 값으로 parseTodos를 불러 봅니다.
console.log(parseTodos(null));
console.log(
parseTodos('[{"id":1}]'),
);
console.log(
parseTodos('{깨진글자'),
);
[]
[ { id: 1 } ]
[]- 첫 방문(
raw === null):null ?? '[]'가'[]'가 되어 빈 배열[]을 돌려줍니다. - 저장된 값이 있을 때: JSON 문자열을 실제 배열
[ { id: 1 } ]로 복원합니다. - 깨진 문자열일 때:
JSON.parse가 던진SyntaxError를catch가 잡아 안전하게 빈 배열[]을 돌려줍니다.
브라우저의 app.js에서는 이 함수를 load()로 만들어 앱을 시작할 때 한 번 부릅니다.
// app.js — 브라우저에서 불러올 때
'use strict';
function load() {
const raw = localStorage
.getItem('todos');
try {
return JSON.parse(
raw ?? '[]',
);
} catch {
return [];
}
}
예제
결과를 먼저 생각한 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.
메모리 객체 store로 localStorage를 흉내 낸 코드입니다. saveItems와 loadItems를 거친 뒤 마지막에 찍히는 두 줄은 무엇일까요?
const store = {};
function saveItems(arr) {
store.cart =
JSON.stringify(arr);
}
function loadItems() {
return JSON.parse(
store.cart ?? '[]',
);
}
console.log(loadItems().length);
saveItems(['커피', '쿠키']);
console.log(loadItems()[1]);
결과 보기
0
쿠키- 처음
loadItems()를 부를 때는store.cart가undefined이므로'[]'를 읽어 길이0이 찍힙니다. saveItems(['커피', '쿠키'])로 저장한 뒤 다시loadItems()를 부르면 복원된 배열의 1번 인덱스인'쿠키'가 찍힙니다.
자주 틀리는 곳
setItem('todos', todos)처럼 배열이나 객체를 그대로 넣는다:localStorage는 문자열만 받으므로JSON.stringify를 거치지 않으면 원래 배열로 되돌릴 수 없는 문자열('[object Object]')이 저장됩니다.- 첫 방문 시
getItem이 돌려주는null을 처리하지 않는다:JSON.parse(null)은null을 돌려주므로, 이어서todos.push(...)나todos.filter(...)를 부를 때Cannot read properties of null이라는TypeError가 납니다.JSON.parse(raw ?? '[]')로 기본 배열을 보장합니다. - 배열을 바꾼 뒤
save()를 부르지 않는다: 화면을 다시 그리는render()와 마찬가지로,todos배열에 할 일을 추가하거나 삭제할 때마다save()를 다시 불러야localStorage의 값도 함께 바뀝니다.
확인 문제
localStorage.setItem에 배열todos를 넣을 때 왜JSON.stringify(todos)를 써야 하나요?
정답
localStorage는 문자열만 저장할 수 있어서, 배열을 그대로 넣으면 구조가 깨진 문자열로 바뀌기 때문입니다. JSON.stringify로 JSON 문자열로 바꿔 저장해야 나중에 JSON.parse로 원래 배열을 복원할 수 있습니다.
- 아직 한 번도
'todos'를 저장한 적이 없는 브라우저에서localStorage.getItem('todos')를 실행해raw에 담으면raw의 값은 무엇이며, 이를 빈 배열[]로 복원하는 식은 어떻게 쓰나요?
정답
raw는 null이며, JSON.parse(raw ?? '[]')로 쓰면 null일 때 '[]'가 들어가 빈 배열 []로 복원됩니다.
- 개발자 도구에서 누군가
localStorage의'todos'값을 잘못된 문자열'abc'로 고쳐 놓았습니다. 앱이 시작될 때SyntaxError로 멈추지 않고 빈 배열[]로 시작하게 하려면JSON.parse를 어떤 문법으로 감싸야 하나요?
정답
try 블록 안에서 JSON.parse(raw ?? '[]')를 돌려주고 catch 블록에서 return [];를 돌려주도록 try / catch로 감쌉니다.