[Zustand] 상태 및 액션 설계 패턴 (작성 중)
목차
1. 상태 설계 패턴
2. 액션 설계 패턴
1. 상태 설계 패턴
① Store 구조화 기본 원칙
Zustand의 Store는 상태, 액션(함수) 계산된 값(Computed Value), 총 세가지로 나눠서 설계를 한다.
const useCountStore = create((set, get) => ({
count: 0,
setCount: () => set((state) => ({count: state.count + 1})),
doubleCount: () => get().count * 2
}));
상단의 소스코드에서 count는 상태에 해당하며
setCount는 count의 값을 1 증가하는 액션을 수행하는, 상태를 변경하는 함수이고
그리고 doubleCount는 get() 함수를 통해 상태의 값을 가져와서 해당 값에 숫자 2를 곱한다.
즉, 계산된 값에 해당한다고 볼 수 있다.
이런 식으로 Store를 구성하는 요소의 역할을 명확하게 구분해두면
코드의 가독성이 좋아지고, 유지보수하기 더 수월해진다.
② 상태 선택 패턴 (Selector)
컴포넌트에서 Store를 호출해서 사용할 경우, 해당 컴포넌트에서 필요한 값만 선택해서 사용할 수 있다.
import {useTestStore} from "zustand";
interface I_TestStore {
Count: number;
Text: string;
Increase: (state: number) => void;
TextChange: (newValue: string) => void;
};
const useTestStore = create<I_TestStore>((set, get) => ({
Count: 0,
Text: "",
Increase: () => set((state) => ({Count: state.Count + 1})),
TextChange: (newValue) => set(() => ({Text: newValue}))
}));
function App() {
const { Count, Text, Increase, TextChange } = useTestStore();
const TextSwitching = () => {
if (Text !== "") {
TextChange("");
} else {
TextChange("Hello");
}
};
return (
<div>
<h3>Click Button's</h3>
<div className="BtnBox">
<button onClick={() => Increase(Count + 1)}>Count +</button>
<button onClick={TextSwitching}>{Text !== "" ? "Hide" : "Show"}</button>
</div>
<div>
<h4>Count: {Count}</h4>
<h4>Text: {Text}</h4>
</div>
</div>
);
}
예제에서 'Count +' 버튼을 클릭하면, TestStore의 Count 상태의 값이 1 증가하고
상태의 값이 변경됐기 때문에, 상태 Count와 연결된 컴포넌트가 Re-Rendering된다.
반대로 'Show(Hide)' 버튼을 클릭하면, TestStore의 상태 Text의 값이 "Hello"나 "" 중 하나로 바뀌고
마찬가지로 상태의 값이 변경됐기 때문에 상태 Text와 연결된 컴포넌트가 Re-rendering된다.
이를 통해서 Store에서 필요한 상태만 가져오고, 해당 상태의 값이 업데이트되면
해당 상태와 연결된 컴포넌트만 선택적으로 Re-rendering되는 것을 알 수 있다.
③ 상태 Update
import {create} from "zustand";
const useTestStore = create((set, get) => ({
Count: 0,
setCount: () => set((state) => ({Count: state.count + 1})), //O
/**
* get(): 상태의 값을 가져오는 함수
*/
}))
Zustand의 Store는 'set()' 함수를 통해서 상태를 수정하는데
Zustand의 전역 상태, Store는 객체의 형태를 하고 있기 때문에
이를 수정하는 set() 함수또한 상태의 불변성을 유지하기 위해서
객체를 반환하는 형태로 코드를 작성해야한다.
④ 복잡한 상태 구조 다루기
Store, 상태의 구조가 중첩된 객체거나 배열 같이 복잡한 구조를 가지고 있다면
아래와 같은 방식으로 작성을 해서 상태의 불변성을 유지해야 한다.
* 중첩된 객체의 경우
const useProfileStore = create((set) => ({
profile: {
Name: "",
Age: 0,
userId: "",
extraInfo: {
Favorites: "",
/**
* {Favorites, isMarried, HomeAddress} 등등이 있다고 가정
*/
}
},
//사용자가 새로 입력한 정보, 혹은 수정한
updateExtraInfo: (newInfo) => set((state) => ({
/**
*'...'연산자를 통해 기존 정보, 새로 추가한 정보(newInfo)를
* 새로운 객체에 분해 할당, 기존 정보와 새로운 정보가 합쳐진
* 새로운 객체를 상태의 값으로 업데이트한다.
*/
profile: {
...state.profile,
extraInfo: {
...state.profile.extraInfo,
...newInfo
}
}
}))
}));
* 배열인 경우
const useArrStore = create((set) => ({
Arrs: ["A", "B", "C"],
//상태 Arrs에 새로운 데이터를 추가하는 addDatas 함수
addDatas: (newData) => set((state) => ({
Arrs: [...state.Arrs, newDatas]
}))
}));
중첩 객체, 배열 어느 쪽이든 기존 값과 새로운 값을 '...'(Spread 연산자)를 통해서
객체나 배열 내부에 존재하는 데이터를 다 꺼내주고,
기존 값 + 새로운 값이 합쳐진 새로운 객체(배열)를 반환하는 것으로 상태의 불변성을 유지시킨다.