반응형
🛺 Zustand란
- React 상태 관리 라이브러리
🚜 create 함수
- Zustand에서 상태 저장소를 만드는 함수
import { create } from "zustand";
interface SeatInterface {
seatNumber: number;
setSeatNumber: () => void;
}
/* Create를 통해 저장소 생성 */
export const useSeatStore = create<SeatInterface>((set) => ({
seatNumber: 0,
setSeatNumber: () => set({ seatNumber: 1 }),
}));
🚍set 함수
- 상태를 업데이트 하는 함수
import { create } from "zustand";
interface SeatInterface {
seatNumber: number;
setSeatNumber: () => void;
}
/* Create를 통해 저장소 생성 */
export const useSeatStore = create<SeatInterface>((set, get) => ({
seatNumber: 0,
// set함수를 통해 상태업데이트
setSeatNumber: () => set({ seatNumber: get().seatNumber + 1 }),
}));
🚄get 함수
- 상태를 조회하는 함수
import { create } from "zustand";
interface SeatInterface {
seatNumber: number;
setSeatNumber: () => void;
}
/* Create를 통해 저장소 생성 */
export const useSeatStore = create<SeatInterface>((set, get) => ({
seatNumber: 0,
// get함수를 통해 상태조회
setSeatNumber: () => set({ seatNumber: get().seatNumber + 1 }),
}));
🚔 컴퍼넌트에서 사용
"use client";
import { useSeatStore } from "app/store/useSeatStore";
import React from "react";
export default function Seat() {
// store의 state
const seatNumber = useSeatStore((state) => state.seatNumber);
// store의 actions
const setSeatNumber = useSeatStore((state) => state.setSeatNumber);
function changeInputEvent(e: React.ChangeEvent<HTMLInputElement>) {
setSeatNumber();
}
return (
<>
<div className="relative z-0 w-full group">
<input
className="block py-2.5 px-0 w-full text-sm text-gray-900 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"
type="number"
value={seatNumber}
onChange={changeInputEvent}
placeholder="값을 입력하세요"
></input>
</div>
</>
);
}
🚈 다중 상태 선택
- 컴퍼넌트에서는 한번에 하나씩만 상태를 가져와야 하지만 useShallow를 이용하면 상태/액션을 겍체 또는 배열 형태로 가져올 수 있다
"use client";
import { useSeatStore } from "app/store/useSeatStore";
import React from "react";
import { useShallow } from "zustand/shallow";
export default function Seat() {
const seatState = useSeatStore(
// useShallow를 통해 상태를 객체 또는 배열을 가져올 수 있다
useShallow((state) => ({
seatNumber: state.seatNumber,
setSeatNumber: state.setSeatNumber,
}))
);
function changeInputEvent(e: React.ChangeEvent<HTMLInputElement>) {
// 객체로 접근
seatState.setSeatNumber();
}
return (
<>
<div className="relative z-0 w-full group">
<input
className="block py-2.5 px-0 w-full text-sm text-gray-900 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"
type="number"
value={seatState.seatNumber}
onChange={changeInputEvent}
placeholder="값을 입력하세요"
></input>
</div>
</>
);
}
🚈 액션 분리
- 가독성을 위해 State와 actions를 분리
import { create } from "zustand";
// 상태 인터페이스
interface State {
seatNumber: number;
}
// 액션 인터페이스
interface Actions {
actions: {
setSeatNumber: () => void;
};
}
// State와 Actions를 병합
export const useSeatStore = create<State & Actions>((set, get) => ({
seatNumber: 0,
actions: {
setSeatNumber: () => set({ seatNumber: get().seatNumber + 1 }),
},
}));
"use client";
import { useSeatStore } from "app/store/useSeatStore";
// 컴퍼넌트에서 사용
export default function Seat() {
const seatNumber = useSeatStore((state) => state.seatNumber);
const { setSeatNumber } = useSeatStore((state) => state.actions);
function onChangeEvent(e: React.ChangeEvent<HTMLInputElement>) {
setSeatNumber();
}
return (
<>
<input
type="number"
name="id"
value={seatNumber}
onChange={onChangeEvent}
placeholder="ID"
className="border p-2 rounded"
></input>
</>
);
}
🚋 초기화
import { create } from "zustand";
interface State {
seatNumber: number;
}
interface Actions {
actions: {
setSeatNumber: () => void;
// 리셋 함수 인자 keys
resetData: (keys?: Array<keyof State>) => void;
};
}
// 초기화 객체 선언 및 타입은 State
const initialState: State = {
seatNumber: 0,
};
export const useSeatStore = create<State & Actions>((set, get) => ({
seatNumber: 0,
actions: {
setSeatNumber: () => set({ seatNumber: get().seatNumber + 1 }),
resetData: (keys) => {
if (!keys) {
set(initialState);
return;
}
// Key리스트가 있을 경우 초기화
keys.forEach((key) => {
set({ [key]: initialState[key] });
});
},
},
}));
🚋 상태의 타입 추론 (Combine)
- 타입을 작성하지 않고 추론하도록 제공하는 combine미들웨어
combine을 사용하면 initialState의 channelId,channelName,channelUrl이 string으로 추론
const initialState = {
channelId: "",
channelName: "",
channelUrl: "",
};
export const useChannelStore = create(
devtools(
// combine을 통해 객체의 key의 value를 통해 타입 추론
combine(initialState, (set) => ({
actions: {
resetState: (keys: Array<keyof typeof initialState>) => {
keys.forEach((key) => {
set({ [key]: initialState[key] });
});
},
setChannelState: <
K extends keyof typeof initialState,
T extends typeof initialState[K]
>(
key: K,
val: T
) => {
set({ [key]: val });
},
},
}))
)
);
🚘 중첩된 객체 변경 (Immer)
- 객체안에 객체의 값을 변경할려면 기존의 속성값을 복사해야한다. 특정 하위속성을 변경할려면 복잡하다.
npm i immer
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface State {
brandInfo: {
brandId: string;
brandName: string;
};
}
interface Actions {
actions: {
setBrandId: (val: string) => void;
setBrandName: (val: string) => void;
};
}
const initialState: State = {
brandInfo: {
brandId: "",
brandName: "",
},
};
export const useBrandStore = create(
// immer를 선언
immer<State & Actions>((set) => ({
...initialState,
actions: {
setBrandId: (val) => {
set((state) => {
/* 객체복사가 아닌 체이닝으로 접근하여 변경 */
state.brandInfo.brandId = val;
});
},
setBrandName: (val) => {
set((state) => {
state.brandInfo.brandName = val;
});
},
},
}))
);
🚋 상태구독 (subscribeWithSelector)
- 스토어 훅에서 subscribe를 사용하면, 스토어의 모든 상태 변경을 구독한다. 그리고 subscribe 함수 반환을 호출하면, 구독을 해제할 수 있다
import { create } from "zustand";
// 특정상태 구독을 위해 subscribeWithSelector 사용
import { subscribeWithSelector } from "zustand/middleware";
interface State {
broadCastId: string;
broadCastName: string;
}
interface Actions {
actions: {
setBrandCastId(val: string): void;
};
}
const initialState: State = {
broadCastId: "",
broadCastName: "",
};
export const useBroadCastStore = create<State & Actions>()(
// subscribeWithSelector으로 감싼다
subscribeWithSelector((set) => ({
...initialState,
actions: {
setBrandCastId: (val) => {
set({ broadCastId: val });
},
},
}))
);
컴퍼넌트에서 사용
"use client";
import { useBroadCastStore } from "app/store/broadCastStore";
import { useEffect, useState } from "react";
export default function BroadCast() {
const setBrandCastId = useBroadCastStore(
(state) => state.actions.setBrandCastId
);
const [double, setDouble] = useState(1);
// 구독처리 및 구독해제
useEffect(() => {
// 리스너이며, broadCastId가 변경이되면 트리거가 된다
const unsubscribe = useBroadCastStore.subscribe(
(state) => state.broadCastId,
() => {
// 2. 변경된 broadCastId를 감지하고, 지역컴퍼넌트의 값을 변경한다
setDouble(double * 2);
}
);
setTimeout(() => {
// 1.5초후 broadCastId를 변경한다
setBrandCastId("NEW ID");
}, 5000);
return () => {
// 컴퍼넌트 소멸시점에는 구독해지를 한다
unsubscribe();
};
}, []);
return (
<div className="container mx-auto pt-2 max-w-90 w-full md:max-w-2xl lg:max-w-5xl">
<div className="min-w-0 flex-1">
<span>Class Warfare</span> <br />
<span>{double}</span>
</div>
</div>
);
}반응형
'React' 카테고리의 다른 글
| 🍏 React Hook (0) | 2025.10.24 |
|---|
댓글