Todo페이지에서 아침/점심/저녁 버튼을 누르면 각 Category 페이지로 이동되는 로직이다. 왼쪽 그림에서 각 버튼을 누르면 아침(빨간색 포스트잇)/점심(노란색 포스트잇)/저녁(파란색 포스트잇)으로 카테고리별로 나뉘는 로직이다. 그런데, 카테고리 페이지에서 수정 API 를 호출하면 상태가 바로 반영이 되는데, Category 페이지를 새로고침하면 다시 예전 값으로 렌더링되는 문제가 있었다. 즉, 수정된 내용이 Category 페이지에서 새로고침하면 반영이 안되었다. 수정 api 는 잘돼서 수정된 내용이 update 되는데 새로고침하면 수정된 데이터가 PostItem 컴포넌트에 반영이 안되고 있음.
1. 버그 코드
- Todo 페이지에서 todolist state를 useNavigate 로 state를 Category 페이지로 전달하는 상황이였다.
하지만…그래도 똑같이 Category컴포넌트를 새로고침하면 업데이트 된 내용이 렌더링 되지 않음.
근본적인 문제 원인은 useNavigate 였다.
아침,점심,저녁 버튼을 눌러야지만 useNavigate를 통해 todolist state 가 넘어오는데, 그 버튼을 누르지 않고 새로고침을 하게 되면 useNavigate에 있던 state 값이 받아와지지가 않기 때문에 화면에 렌더링되지 않는 거였다. 즉, navigate 를 안통하면 update가 되지 않는 것이였다.
만약에 useNavigate로 state를 전달하지 않고, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달했다면 변경이 되었을 것이다.
3. 해결책
1️⃣상태관리를 쉽게 하기 위해서 전역상태관리 라이브러리를 쓸까?혹은 context api 가 대안책 useNavigate로 상태가 넘어와지지 않아. 서로 다른 두 컴포넌트에 같은 데이터가 필요한 경우, 각 컴포넌트가 부모-자식 관계로 되어있지 않은 이상 각 컴포넌트 간의 직접적인 데이터 전달이 어려워!!
2️⃣ 우리가 수정 API 를 호출해서 수정된 결과를 서버에 저장했으니 Category 페이지에서 get API 를 호출해서 다시 새로운 데이터를 불러오게 해서 categoryTodolist state 에 담을까?
3️⃣ Todo 메인 페이지와 Category 페이지를 분리하지 않고 Todo 메인페이지에서 하나로 관리해서 부모-자식 컴포넌트로 만들어서 데이터 전달하게 하기
4️⃣Router 에서 Todo페이지와 Todo/Category 를 감싸는 컴포넌트를 만들고 거기에 todolist state를 공유해서 하나로 관리한다.(하지만 이는 사본으로 동기화 되는 과정이 필요한데 상태를 동기화 해야할 필요가 있기에 복잡하고 오류가 발생할 수 있다.)
이 4가지 중에 우리는 처음에 2번으로 선택했었다. 투두리스트가 작은 프로젝트라서 굳이 전역상태관리 라이브러리가 필요할까 했다. 그리고 무한정 투두리스트 항목을 입력할 수 있고, 서버에서 페이징 없이 한번에 모든 투두리스트를 불러올 수 있기에 todolist 상태에 입력된 배열이 몇천개씩 길어질 우려(물론 그럴 일 없지만)로 인해 2번으로 했었다.
get api 를 Category 페이지에서 재호출 할 경우에 투두리스트 항목을 수정하고 나서도 다시 get api 를 불러온다. 이 말인 즉슨, API 호출에 따른 네트워크 서버 비용이 발생할 수 있고, API 호출이 불러오는데 네트워크 환경에 따라 호출 속도가 달라서 ux 경험에 치명적일 수도 있다는 것이다.
서로 같은 데이터를 다루는데 2개의 상태... 2개의 useState...NoNo
Todo 페이지의 todolist 의 상태와 Category 페이지의 categoryTodolist 의 state는 state 이름은 다르지만 다루는 데이터는 같다. 즉, 각 컴포넌트가 완전히 독립적이고, 동일한 초기값을 가지지만 서로의 상태에 영향을 주지 않을 경우에는 각 컴포넌트에서 'useState'를 사용하여 동일한 초기 상태를 가진 상태를 만드는 것은 가능하다. 하지만, 우리는 동일한 상태를 공유하고, 상태변경이 Todo 컴포넌트와 Category 컴포넌트가 서로에게 영향을 미치기 때문에 상위 컴포넌트에서 관리하거나 전역상태관리를 사용하는 것이 좋다.
4번으로 해도 문제가 있었다.
전역 상태의 필요성
더불어 우리가 우려했던 투두리스트의 배열의 항목이 길어지면, 전역변수 쓰기가 좀 그렇지 않나 했었는데.. 왜냐하면, 우리는 자바스크립트를 배울 때 전역변수를 남용하는 것은 좋지 않다고 배웠기 때문이다. 그런데, 이번에 전역상태를 공부하면서 경우에 따라서는 전역상태가 필요하다는 것을 알게되었다.
서로 다른 컴포넌트가 사용하는 상태의 종류가 다르다면, 꼭 전역 상태일 필요는 없다. 하지만, 서로 다른 컴포넌트가 동일한 상태를 다룬다면, 이 출처는 오직 한 곳이어야 한다. 만일 사본이 있을 경우, 두 데이터는 서로 동기화(sync) 하는 과정이 필요한데, 이는 상태관리 문제를 어렵게 만든다. 사본이 어렵다면 아래 글을 읽어보자. 따라서, 한곳에만 상태를 저장하고 접근할 필요가 있다. '하나의 출처' 는 '전역 공간' 이라고 볼 수 있다.
1) 전역 상태에서의 "데이터의 무결성" : 데이터의 정확성을 보장하기 위해 데이터의 변경이나 수정 시 제한을 두어 안정성을 저해하는 요소를 막고 데이터 상태들을 항상 옳게 유지하는 것.
2) Single Source of truth : 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 온다. 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 오도록 하자. Single source of truth(신뢰할 수 있는 단일 출처) 원칙은 프론트엔드 뿐만 아니라 다양한 곳에서 언급되는 원칙이다.
🔸사본이 무슨말인가?? 사본 예제코드
두 개의 서로 다른 컴포넌트가 각각 useState를 사용하여 독립적인 상태를 가지되, 한 컴포넌트에서 상태가 변경될 때마다 다른 컴포넌트의 상태도 같이 업데이트하는 경우를 생각해볼 수 있다.
import React, { useState, useEffect } from 'react';
function Parent() {
const [syncState, setSyncState] = useState('');
return (
<div>
<Component1 syncState={syncState} setSyncState={setSyncState} />
<Component2 syncState={syncState} />
</div>
);
}
function Component1({ syncState, setSyncState }) {
const [localState, setLocalState] = useState('');
const handleChange = event => {
setLocalState(event.target.value);
setSyncState(event.target.value); // sync with the other state
};
return (
<input type='text' value={localState} onChange={handleChange} />
);
}
function Component2({ syncState }) {
const [localState, setLocalState] = useState('');
// Whenever syncState changes, update localState.
useEffect(() => {
setLocalState(syncState);
}, [syncState]);
return (
<input type='text' value={localState} readOnly /> // This input just display the value, and cannot be edited directly.
);
}
이 코드에서 Component1과 Component2는 각각 독립적인 상태 localState를 가지고 있다. 또한 Component1에서 localState가 변경될 때마다 syncState도 함께 변경되어, 이 변경 사항이 Component2의 localState로 반영된다. 그러나 이런 동기화 과정은 복잡하고 버그를 만들기 쉽다. 이런 이유로, 일반적으로 여러 컴포넌트가 동일한 상태를 공유해야 하는 경우 상태를 상위 컴포넌트에서 관리하거나, 전역 상태 관리 라이브러리를 사용하여 상태를 관리하는 것이 좋다. 이렇게 함으로써 상태의 동기화를 단순화하고 일관성을 유지할 수 있다.
그렇다면, 전역상태를 할경우 투두리스트 배열 길이가 막 몇십억개 될경우?
(그럴 일 없겠지만) 투두리스트 배열이 몇천개여도 용량이 크지 않아서 괜찮다고 한다. 하지만 진짜 막 몇십업개? 엄청난 대량데이터일경우? 이미지가 들어간 투두리스트 일경우?
대량의 데이터를 전역 상태로 관리하는 것은 메모리 사용량과 성능에 영향을 줄 수 있다. 메모리를 많이 차지 하게 되어 애플리케이션의 성능을 저하시킬 수 있다. 그래서 대량의 데이터를 관리할 때는 데이터를 필요한 만큼만 가져오고, 사용자가 더 많은 데이터를 요청할 때 추가로 로드하는 방식(페이징이나 무한스크롤)을 사용하는게 좋다. 이를 사용하면 클라이언트의 메모리 사용량을 줄이고, 렌더링 성능을 향상시킬 수 있고, 필요한 데이터만 로드함으로써 네트워크 트래픽을 최적화하는데 도움이 된다.
우리는 사실 서버가 제공된 서버이기 때문에 서버 페이징이 되지 않아서, 이방법을 쓰지 못한다. 어차피 투두리스트 배열이 많아져도 용량이 크지 않아서 전역상태로 관리해도 상관없다.
데이터가 많다는 기준이 얼마나일까?
1) 메모리 사용: JavaScript의 경우, 대략 수 천 개의 항목까지는 대부분의 브라우저에서 안정적으로 처리할 수 있다. 그러나 이 기준은 항목 하나의 복잡성, 내용, 메모리 사용 등에 따라 달라진다. 2) 성능: 데이터가 증가하면 애플리케이션의 렌더링 성능이 저하될 수 있다. 대량의 데이터를 처리해야 하면, 효율적인 데이터 처리 기법(예: 데이터의 레이지 로딩, 페이징)을 사용해야 할 수 있다. 3) 사용자 경험: 너무 많은 데이터는 사용자 인터페이스와 경험에도 영향을 줄 수 있다. 예를 들어, 한 번에 화면에 표시되는 항목의 수가 너무 많으면, 사용자가 정보를 찾거나 이해하는데 어려움을 겪을 수 있다.
결론적으로 우리는 전역상태관리 라이브러리, 혹은 context API를 쓸 필요가 생겼다!
이미 get API 재호출을 했으니 리팩토링 할때 context API 와 전역상태관리 라이브러리를 써보자