Zustand를 통한 React 상태관리
개요
여러 페이지에 걸쳐 사용자로부터 데이터를 입력받아 최종적으로 입력받았던 데이터들을 한 번에 보내려면 어떻게 해야 할까요?
초기에는 SessionStorage에 데이터를 저장해서 마지막 페이지에서 가져다가 쓰는 방식으로 코드를 설계하였습니다.
하지만 세션을 통해 데이터를 관리하는 건 좋지 않은 방법이었습니다. 이유는 다음과 같습니다.
-
최대 5M까지 밖에 데이터를 넣을 수 없다. (초기에 좋은 설계는 아니었지만 이미지 자체를 저장하려 했기 때문에 이런 용량 제한도 설계에 영향을 미침)
-
보안 문제 (세션에 저장된 값을 쉽게 누구나 확인 가능)
-
Json형태로 가공 필요 (JSON.parse / JSON.stringify 함수를 통해 매번 문자열로 데이터를 파싱해야 함)
-
속도 문제
특히 데이터를 보내고 바로 이동하는 페이지에서 세션으로부터 특정 값을 읽어올 때 새로고침 해야 하는 문제도 발생하였습니다.
마지막 페이지에서 Next버튼을 누르면 세션으로부터 값을 읽어와 api를 보냅니다.
응답값으로 특정 id값을 받아와 세션에 저장 후 현제 세션에 저장되어있는 id의 개수만큼 처리되고 있는 동영상을 나타내는 페이지입니다.
단 영상과 달리 초기의 세션을 이용한 방법에서는 새로고침을 해야 정상적으로 처리되고 있는 동영상의 개수가 집계되었습니다.
Zustand
React에서는 전역상태 관리 라이브러리를 사용해 보다 체계적인 상태관리가 가능합니다. 대표적으로 Redux, Recoil등이 있습니다.
사실 초기에 Redux를 사용해 전역변수를 관리하려했지만 프로젝트 종료까지 남은 시간도 많지 않았고 Redux를 배워본 적이 없었기 때문에 도입하기에 부담이 있었습니다.
그때 멘토님깨서 추천해주신 라이브러리가 바로 Zustand입니다. (감사합니다 멘토님😂)
Zustand의 장점은 다음과 같습니다.
- Context Api의 불필요한 렌더링 방지
- 간결한 초기설정과 직관적이고 복잡하지 않은 전역변수를 다루는데 유용
- Redux와 다르게 providers로 감싸주지 않아도 됨
사용하는데 있어 가장 좋았던 부분은 아무래도 사용하기 쉽다 는 부분이 가장 컸던 것 같습니다.
1. 설치
yarn add zustand
npm install zustand #npm을 사용할 경우
2. Store생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import create from "zustand";
interface testDataInfo {
//타입스크립트 사용 시 필요 (js라면 지우기)
testData: String[];
setTestData: (select: String[]) => void;
}
export const useStore = create<testDataInfo>((set) => ({
testData: [],
setTestData: (select) => {
set((state) => ({ ...state, testData: select }));
},
}));
testData라는 문자열 배열을 저장하는 전역변수를 명시한 예시입니다.
3. 사용하기
1
2
3
4
5
6
7
8
9
10
import { useStore } from "../../store/store";
function testCompoents() {
const { testData, setTestData } = useStore(); //zustand 전역변수
...
console.log(testData); //저장한 값 꺼내기
setTestData("저장 할 데이터"); //값 저장하기
사용할 특정 컴포넌트에서 import하며 다음과 같이 사용할 수 있습니다.
4. persist (옵션)
이제 세션 스토리지에서 저장하던 값을 Zustand를 통해 전역변수를 관리하게 되었습니다.
구현 난이도도 쉽고 큰 문제점은 없었기 때문에 잘 사용하고 있었지만 한 가지 문제점이 있었습니다.
여러 페이지로부터 데이터를 받아서 저장하는데 만약 사용자가 새로고침을 하게 된다면 기존에 저장하고 있던 모든 데이터들이 다 날아가는 문제점이 말이죠..
제가 사용하던 프로젝트에서는 프론트쪽에서 데이터를 저장하다가 한 번에 보내주는 방식으로 설계를 구성했기 때문에 이 문제를 해결하기 위해 persist 옵션을 추가로 사용하였습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import create from "zustand";
import { persist } from "zustand/middleware";
export const useStore = create<any>(
persist(
(set) => ({
testData: [],
setTestData: (select) => {
set((state) => ({ ...state, testData: select }));
},
}),
{ name: "user-StoreName" }
)
);
특별한 옵션이 없다면 localStorage에 전역변수를 넣어 저장합니다.
사용하는 코드는 위와 같기때문에 store.ts파일부분의 설정만 해주시면 됩니다.
이상하게도 persist옵션을 넣으면 type명시에서 오류가 발생해서 불가피하게 any타입을 넣었습니다.
아무리 찾아도 해결이 안되서 혹시 해결하신분 있으시다면 댓글 부탁드립니다ㅜㅜ..
“persist를 사용해서 다시 스토리지에 저장하면 처음과 같아지는 것 아닌가요?”
사실 처음에는 위와 같은 의문점이 들었습니다. 결국 스토리지에 넣어서 저장을 하게된다면 같은 이슈를 겪는게 아닌지 의아했거든요.
“하지만 새로고침과 같은 예외 상황을 제외하고는 일반적으로 zustnad에서 불러오는게 빠르기 때문에 충분히 zustand의 이점을 살리고 있다고 할 수 있어요.” (멘토님 답변)
실제로 위와 같이 새로고침해서 데이터를 다시 읽어오는 문제는 발생하지 않았습니다.
겪었던 오류
1
react.development.js:1676 Uncaught TypeError: dispatcher.useSyncExternalStore is not a function
저 같은 경우 버전 호환성과 관련된 문제였습니다.
React버전을 18에서 17로 낮춰 문제를 해결하였습니다. 참고했던 링크
-
package.json의 react와 react-dom의 버전을 확인합니다.
-
버전을 낮춥니다.
yarn add react@17.0.2
yarn add react-dom@17.0.2
저는0 17.0.2버전으로 낮추었습니다.
-
package.json파일을 통해 정상적으로 버전이 바뀌었는지 확인합니다.
댓글남기기