과제를 수행하면서 category 추가나 component분리 등은 구현 문제였기 때문에 크게 어렵지 않았다. 하지만 가장 큰 문제는 local storage 구현이었는데, 근무 시간 이틀을 통째로 잡아먹고도 다른 인턴분과 사수님께 여러 질문을 해가면서 겨우겨우 구현에 성공했다.
Local Storage란?
데이터는 새로고침/재접속 시에 초기화가 된다. 이러한 데이터를 기억하기 위해서는 따로 저장공간에 데이터를 저장해야 하는데, 크게 1. 서버로 보내서 데이터베이스에 저장하거나 2.브라우저가 가지고 있는 임시 저장공간(localStorage)에 저장하는 방법이 있다.
local storage는 session storage와 함께 web storage중 하나인데, 이는 데이터를 서버가 아닌 클라이언트에 저장 할 수 있도록 지원하는 기능이다. 쿠키와 유사한 기능을 가졌지만, 쿠키는 4KB까지 밖에 저장공간을 가지지 못하는 반면 웹 스토리지는 약 5MB정도의 저장공간을 가질 수 있다.
storage에 저장되는 데이터의 구조는 'key' - 'value' 로 이루어져 있으며, 데이터는 문자열로만 저장,반환된다.
크롬의 경우 F12 - Application - Local Storage부분에서 확인 가능하다.
// 데이터 쓰기
localStorage.setItem("key", value);
// 데이터 읽기
localStorage.getItem("key");
// 데이터 삭제
localStorage.removeItem("key");
// 모든 데이터 삭제
localStorage.clear();
// 저장된 값의 개수
localStorage.length;
local storage 구현에 앞서 to-do list를 구현한 모습을 생각해보자.
두 state가 update될 때 마다 localStorage에 값을 update해주면 되겠다고 생각했다.
key값으로는 EditableToDoList의 Index값을 주고 value에 ToDo item 컴포넌트({idx, content, done})를 array로 주기로 했다.
이하는 구현과정에서 있었던 문제와 해결방법들이다.
1) cannot find localStorage
react도 18버전부터 client-side를 렌더하기 전 server-side 렌더를 수행하는데, localStorage가 들어있는 window 객체는 client-side에만 존재한다. 페이지가 client에 로드되고 window 객체가 정의될 때까지 localStorage에 접근할 수 없기 때문에 발생한 문제.
따라서 useEffect()를 사용하여 문제를 해결한다. useEffect는 렌더링 시 실행되고 client side에서만 실행되기 때문.
이외에도 JSON.parse를 사용하려면 [ { ... } , { ... } ] or [ value, value ] 의 양식을 지킨 string이어야 한다던가, index를 순서대로 정하면 localStorage에서 값을 불러왔을 때 중복 key 문제가 발생하는 등의 여러 기본적인 문제를 해결했다.
2) localStorage에 값이 있는지 어떻게 체크하는가?
List의 index값들을 key로 가지고 있는데, 이때 key 값은 list가 생성되는 시기의 Date값이기 때문에 임의로 알 수 있는 방법이 없었다. 또한 이 list가 비어있는지도 모두 일일이 확인해야 했고, localStorage에 어떤 값들이 저장되는지 알 수 없었기 때문에 너무 비효율적이었다.
처음에는 다음과 같이 모든 key값을 순회하며 key값이 Date값인지, 비어있는지 일일이 확인했다.
Object.keys(localStorage).forEach(function (key) {
if (Number.isInteger(Number(key))) {
if (localStorage.getItem(key) === "[]") localStorage.removeItem(key);
...
}
});
해결 : LIST라는 key값을 가진 data를 생성하여 value로 list의 index값들을 넣어주기로 했다.
이렇게 하면 나중에 localStorage에서 값을 받아와 새로 list를 생성할 때도 이 value에 대해 map으로 index값을 순회하기 편할 뿐만 아니라 LIST의 value가 존재하는지만 확인하면 localStorage에서 값을 받아올지 말지 간다하게 결정할 수 있기 때문이다.
3) 아래 컴포넌트로 인자를 어떻게 전달해 주어야 하는가?
앞선 구현에서 각 state는 다음과 같이 관리되었다.
const addList = (e) => {
const newTodoList = list.concat({
idx: +new Date(),
item :
<EditableToDoList
key={listitem.idx}
list={list}
setList={setList}
listIdx={listitem.idx}
/>,
});
setList(newTodoList);
};
하지만 우리가 localStorage에 저장한 값은 <ToDoItem>컴포넌트였고, 이를 불러오는 곳에 존재하는 <EditableToDoList> 컴포넌트는 이 값을 받을 수 있는 props가 없었다. 즉,구조의 가장 아래부분에서부터 update된 값을 보여주는 방식으로 구현을 했더니 위에서 값이 주어졌을 때, 아래로 값을 넣어주는 방법을 몰랐던 것.
해결 : 새롭게 위에서도 값을 update해 줄 수 있도록 EditableToDoList에 itemArray라는 props를 추가하여 아래 component로 전달시켜주기로 했다.
4) useEffect에서 Hook을 사용했을때?
이런 저런 고생을 하며 localStorage에 원하는 형식으로 값을 저장하는 데 성공했다.
useEffect에서 setList를 사용해 값을 list를 갱신해주려는데 문제가 발생했다.
useEffect(() => {
//첫 render 후, local storage에 값이 저장되어있는지 확인한다.
if (localStorage.getItem("LIST") === null || localStorage.getItem("LIST") === "[]") {
console.log("No Storage Data");
} else {
//값이 저장되어있는 경우 local storage에서 list에 대한 정보를 가져온다.
//"LIST"에는 list들의 index가 저장되어 있다.
const listArrays = JSON.parse(localStorage.getItem("LIST"));
//index를 따라 순회하면서 list를 추가한다.
listArrays.map((listIndex) => {
const itemArray = JSON.parse(localStorage.getItem(String(listIndex)));
return setList(
list.concat({
idx: listIndex,
item: <EditableToDoList list={list} setList={setList} todolist={itemArray} />,
}),
);
});
}, []);
위와 같이 구현하려 했는데, EditableToDoList에 todolist값이 제대로 들어가지 않는 것을 확인했다.
//index.tsx의 return 부분
return (
<>
<button onClick={addList}>[+New List]</button>
<div className="inline-flex">
{list.map(((listitem)) => (
<EditableToDoList key={listitem.idx} list={list} setList={setList} listIdx={listitem.idx} todolist={listitem.item} />
))}
</div>
</>
);
이는 return부분에서 문제를 확인할 수 있었는데, 여기서 todolist에 listitem.item을 인자로 주었더니 item은 또 component이고 이상하게 props가 전달되고 있었던 것.
해결 : list의 item을 EditableToDoList 컴포넌트가 아닌 itemArray만을 인자로 가지게 했다.
EditableToDoList를 부르면서 다른 list, setList등의 state와 hook을 이미 전달하기 때문에 중복되는 코드였던 것.
5) state update가 왜 안될까?
하지만 이 문제를 해결했지만 list가 제대로 update가 되어있지 않음을 확인했다.
console.log로 list를 확인해보니 가장 마지막 list 값만이 저장되어있었는데, 이는 setList의 문제에 있었다.
//수정 전
return setList(
list.concat({
idx: listIndex,
item: <EditableToDoList list={list} setList={setList} todolist={itemArray} />,
}),
);
//수정 후
let i = [];
listArrays.map((listIndex) => {
const itemArray = JSON.parse(localStorage.getItem(String(listIndex)));
i.push({ idx: listIndex, itemArray });
});
setList(i);
예를 들어 1번 index의 list와 2번 list 두개의 list를 업데이트 해야하는 과정을 생각해보자. 수정 전의 process는 다음과 같다.
① 현재 비어있는 list []와 1번 list값을 concat하여 update할 것을 queue에 올린다. list = [ 1 ]
② 다시 2번 list에 대해서도 비어있는 list[]와 2번 list값을 concat하여 update한다. list = [ 2 ]
③ 결국 queue를 실행하면서 최종적으로 list는 [ 2 ] 로 update된다.
따라서 i라는 변수를 생성하여 모든 list를 concat하고 이를 update해주는 방법을 사용했다.
'개발 > React' 카테고리의 다른 글
React - 7) DOM, Rendering (0) | 2022.07.04 |
---|---|
React - 5) To-Do List (0) | 2022.06.30 |
React - 4) State (0) | 2022.06.24 |
React - 3) Interactivity (0) | 2022.06.24 |
React - 2) React Design (0) | 2022.06.23 |