# LocalStorage를 일기 데이터베이스로 사용하기
새로운 일기를 추가한 다음 새로고침 하면 기존의 dummuData 말고 새로 작성한 일기들은 사라진다
왜 그럴까?
자바스크립트는 Client Side이기 때문에 웹 브라우저에서만 동작한다
자바스크립트의 state 는 휘발성 메모리라서 새로고침 시 모두 사라지게 된다
원래는 Date Base에 값을 저장하고 꺼내오고 해야하지만 이번 강의에는 DB 를 대신해서
LocalStorage 를 사용해보도록 하자
# Web Storage API
key-value 쌍을 쿠키보다 훨씬 직관적으로 저장할 수 있는 방법을 제공하는 기능이다
# Storage 종류
session storage :
출처에 대해 독립적인 저장 공간을 제공하는데 페이지 세션이 유지되는 동안만 제공한다
즉 브라우저가 열려있는 동안만 유요하며 웹 브라우저가 꺼지면 데이터가 초기화된다
local storage :
브라우저를 닫았다가 열어도 데이터가 남아있다
유효기간 없이 데이터를 저장하며 브라우저 캐시 또는 로컬 저장 데이터를 지워야지 데이터가 사라진다
localStorage 사용해보자!
App 컴포넌트에서 localStorage 에 저장하는 코드를 작성하고 확인해보자
useEffect(() => {
localStorage.setItem("key", 10);
}, []);
LocalStorage에 저장되어 있는 것을 확인할 수 있다
LocalStorage 에 객체를 저장하게 되면 브라우저 스토리지는 인식할 수 없다
따라서
직렬화 JSON.stringify
가 필요하다!
useEffect(() => {
localStorage.setItem("key", 10);
localStorage.setItem("item1", 10);
localStorage.setItem("item2", "20");
localStorage.setItem("item3", JSON.stringify({ value: 30 }));
}, []);
# localStorage 데이터 꺼내오기
localStorage.getItem 사용해서 꺼내오면 된다
useEffect(() => {
const item1 = localStorage.getItem("item1");
const item2 = localStorage.getItem("item2");
const item3 = localStorage.getItem("item3");
console.log({ item1, item2, item3 });
}, []);
localStorage에 저장될 때 기본적으로 문자열로 바뀌어서 저장된다
따라서
역직렬화 JSON.parse
로 직렬화된 객체를 다시 자바스크립트 객체로 복원 해야한다
# LocalStorage 적용하기
일기 데이터를 로컬 스토리지에 저장하자
변경되는 로직 처리는 모두 reducer 에서 일어난다
따라서 reducer 로직 내에서 localStorage에 저장해주는 로직을 추가하자
const reducer = (state, action) => {
let newState = [];
switch (action.type) {
case "INIT": {
return action.data;
}
case "CREATE": {
newState = [action.data, ...state];
break;
}
case "REMOVE": {
newState = state.filter((it) => it.id !== action.targetId);
break;
}
case "EDIT": {
newState = state.map((it) =>
it.id === action.data.id ? { ...action.data } : it
);
break;
}
default:
return state;
}
localStorage.setItem("diary", JSON.stringify(newState));
return newState;
};
새로운 일기 작성 시 localStorage 에 잘 들어오는 것을 확인할 수 있다
그럼 기존에 저장된 일기를 삭제하기 위해 삭제 버튼과 로직을 추가해보자
const handleRemove = () => {
if (window.confirm("정말 삭제하시겠습니까?")) {
onRemove(originData.id);
navigate("/", { replace: true });
}
};
삭제하니 localStorage 에서도 정상적으로 삭제된다!
localStorage 의 데이터를 화면에 렌더링 되도록 해보자
localStorage의 데이터를 data state의 초기값으로 설정하면 된다
useEffect(() => {
const localData = localStorage.getItem("diary");
if (localData) {
const diaryList = JSON.parse(localData).sort(
(a, b) => parseInt(b.id) - parseInt(a.id)
);
dispatch({ type: "INIT", data: diaryList });
}
}, []);
새로고침해도 저장된 일기가 사라지지 않고 잘 렌더링 된다!!!
# 에러 해결 기록
useEffect(() => {
const localData = localStorage.getItem("diary");
if (localData) {
const diaryList = JSON.parse(localData).sort(
(a, b) => parseInt(b.id) - parseInt(a.id)
);
dataId.current = parseInt(diaryList[0].id) + 1;
dispatch({ type: "INIT", data: diaryList });
}
}, []);
로 하니 diaryList[0].id 를 읽을 수 없다는 오류가 발생한다
또한 새로운 일기를 저장하면 같은 일기가 두개씩 저장 된다
왜 그렇지..?
# 해결 방법
일기 저장 함수에서 같은 로직을 두번 호출하고 있었다
const handleSubmit = () => {
if (content.length < 1) {
contentRef.current.focus();
return;
}
if (
window.confirm(
isEdit ? "일기를 수정하시겠습니까?" : "새로운 일기를 작성하시겠습니까?"
)
) {
if (!isEdit) {
onCreate(date, content, emotion);
} else {
onEdit(originData.id, date, content, emotion);
}
}
onCreate(date, content, emotion);
navigate("/", { replace: true });
};
한번만 호출하게 수정하니 아주 잘 된다
코드 칠 때는 항상 정신 차리자!!!!
# 프로젝트 최적화
컴포넌트와 연산 관련 최적화를 진행해보자
어떤 부분에서 연산의 낭비가 일어나고 있는지 확인해보자
1. 자식 컴포넌트로의 불필요한 연쇄 렌더링 발생
헤더에서 날짜를 변경하니 필터도 렌더링 되고 있다
그럼 우선 Home 컴포넌트를 살펴보자
const increaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
);
};
const decreaseMonth = () => {
setCurDate(
new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
);
};
날짜 변경 함수의 로직은 다음과 같다
Home 컴포넌트의 자식 컴포넌트인 DiayList 함수도 살펴보자
Home 컴포넌트가 렌더링되면 자식 컴포넌트인 DiaryList 컴포넌트도 렌더링 된다
또한 DiaryList 컴포넌트의 자식 컴포넌트인 Control Menu 컴포넌트의 렌더링도 발생하게 된다
따라서!
ControlMemu 컴포넌트 렌더링을 막아보자
React.memo를 적용하자
const ControlMenu = React.memo(({ value, onChange, optionList }) => {
return (
<select
className="ControlMenu"
value={value}
onChange={(e) => onChange(e.target.value)}
>
{optionList.map((it, idx) => (
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
});
그런데
onChange 함수를 별도의 useCallback 처리를 하지 않았음에도 리렌더링이 발생하지 않는다
왜 그럴까?
ControlMenu에게 전달한 onChange 는 useState 가 반환하는 상태 변화 함수이기 때문이다
상태 변화 함수는 렌더링이 일어나도 동일한 ID를 보장한다
따라서 상태 변화 함수를 내려주자!
2. DiaryItem 컴포넌트 필터 변경 시 불필요한 렌더링 발생
필터 값을 변경하면 DiaryList 컴포넌트의 필터 값도 변경된다
DiaryList 컴포넌트의 자식 컴포넌트인 DiaryItem 컴포넌트도 리렌더링된다!
아이템의 렌더링은 굉장히 위험하다
페이지 버벅임을 유도하기 때문이다
따라서
DiaryItem 컴포넌트에 React.memo를 적용하자
3. 일기 수정 시 불필요한 렌더링 발생
오늘의 일기를 수정할 때 content state가 계속해서 변한다
이는 EmotionItem 에도 불필요한 렌더링을 발생시킨다
따라서
EmotionItem에도 React.memo 를 적용하자
그런데 React.memo를 적용해도 해결 안 된다!!!
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_descript,
onClick,
isSelected,
}) => {
.....
EmotionItem 이 전달받는 props 중에 함수도 있기 때문이다 (onClick)
부모 컴포넌트인 DiaryEditor에서 useCallBack 적용하면 해결 완료!
const handleClickEmote = useCallback((emotion) => {
setEmotion(emotion);
}, []);
# 새로 알게 된 점
프로젝트의 최적화는 강의를 보고 따라할 때는 쉽지만 혼자 스스로 찾을 때는 어려운 것 같다
아마도 최적화 경험이 많이 없기 때문이겠지?
기능을 구현한 것에서 그치지 않고 최적화를 통해 최고의 성능을 내는 서비스를 만드는 개발자가 되어야겠다
또한 사소한 실수(오타나 같은 로직 두번 넣기 등)는 한번 잘못 입력하면 눈에 잘 띄지가 않는 것 같다
이러한 실수를 애초에 하지 않도록 더욱 주의해서 개발해야겠다
'TIL > 프로그래머스 데브코스' 카테고리의 다른 글
클라우딩 어플리케이션 엔지니어링 TIL Day 43, 44 - React & Bootstrap 웹 프로젝트 제작 (1~2) Bootstrap 사용 방법 및 실습 (0) | 2024.03.21 |
---|---|
클라우딩 어플리케이션 엔지니어링 TIL Day 39, 40, 41, 42 - 감정 일기장 만들기 (8 ~ 마치면서) (0) | 2024.03.01 |
클라우딩 어플리케이션 엔지니어링 TIL Day 36 - 감정 일기장 만들기 (5) (0) | 2024.02.29 |
클라우딩 어플리케이션 엔지니어링 TIL Day 35 - 감정 일기장 만들기 (4) (0) | 2024.02.27 |
클라우딩 어플리케이션 엔지니어링 TIL Day 34 - 감정 일기장 만들기 (3) (1) | 2024.02.27 |