상태 관리

cloud2000·2025년 11월 22일

로컬 상태 관리와 전역 상태 관리 방법에 대해 알아보자

로컬 상태관리는 useState나 useReducer와 같은 훅을 사용하고 전역 상태는 Context api, Redux-toolkit, Zustand 등을 사용할 수 있다.

1. Local State

  • 최상위 컴포넌트
"use client";
import { useState } from "react";
import Count from "./components/Count";
import CountDisplayExternal from "./components/CountDisplayExternal";

const LocalStatePage = () => {
	const [count, setCount] = useState(0);

	const increment = () => setCount((count) => count + 1);
	const decrement = () => setCount((count) => count - 1);
	const reset = () => setCount(0);

	return (
		<>
			<Count
				count={count}
				increment={increment}
				decrement={decrement}
				reset={reset}
			></Count>
			<CountDisplayExternal count={count}></CountDisplayExternal>
		</>
	);
};

export default LocalStatePage;
# ./component/count.tsx
import CountButtons from "./CountButton";
import CountDisplay from "./CountDisplay";

const CountPage = ({
	count,
	increment,
	decrement,
	reset,
}: {
	count: number;
	increment: () => void;
	decrement: () => void;
	reset: () => void;
}) => {
	return (
		<>
			<CountDisplay count={count}></CountDisplay>
			<CountButtons
				increment={increment}
				decrement={decrement}
				reset={reset}
			></CountButtons>
		</>
	);
};

export default CountPage;

# ./component/countdisplay.tsx
const CountDisplay = ({ count }: { count: number }) => {
	return (
		<>
			<p>Count: {count}</p>
		</>
	);
};

export default CountDisplay;

# ./component/countdisplayexternal.tsx
const CountDisplayExternal = ({ count }: { count: number }) => {
	return (
		<>
			<p>Count display external: {count}</p>
		</>
	);
};

export default CountDisplayExternal;


# ./component/countbutton.tsx
const CountButtons = ({
	increment,
	decrement,
	reset,
}: {
	increment: () => void;
	decrement: () => void;
	reset: () => void;
}) => {
	return (
		<>
			<button onClick={increment}>증가</button>
			<button onClick={decrement}>감소</button>
			<button onClick={reset}>리셋</button>
		</>
	);
};

export default CountButtons;

2. Context API

import { createContext } from "react";

interface CountContextType {
	count: number;
	increment: () => void;
	decrement: () => void;
	reset: () => void;
}

export const CountContext = createContext<CountContextType | null>(null);
import { useState } from "react";
import { CountContext } from "../contexts/CountContext";

const CountProvider = ({ children }: { children: React.ReactNode }) => {
	const [count, setCount] = useState(0);

	const increment = () => setCount((count) => count + 1);
	const decrement = () => setCount((count) => count - 1);
	const reset = () => setCount(0);

	return (
		<>
			<CountContext value={{ count, increment, decrement, reset }}>
				{children}
			</CountContext>
		</>
	);
};

export default CountProvider;
import { useContext } from "react";
import { CountContext } from "../contexts/CountContext";

export const useCountContext = () => {
	const context = useContext(CountContext);

	if (!context) {
		throw new Error(
			"useCountContext는 CountContext로 감싼 컴포넌트 안에서만 호출할 수 있습니다."
		);
	}

	return context;
};
  • 사용하는 곳에서
"use client";
import Count from "./components/Count";
import CountDisplayExternal from "./components/CountDisplayExternal";
import CountProvider from "./providers/CountProvider";

const ContextPage = () => {
	return (
		<>
			<CountProvider>
				<Count />
				<CountDisplayExternal />
			</CountProvider>
		</>
	);
};

export default ContextPage;
import CountButtons from "./CountButton";
import CountDisplay from "./CountDisplay";

const CountPage = () => {
	return (
		<>
			<CountDisplay />
			<CountButtons />
		</>
	);
};

export default CountPage;

import { useCountContext } from "../hooks/useCountContext";

const CountButtons = () => {
	const { increment, decrement, reset } = useCountContext();
	return (
		<>
			<button onClick={increment}>증가</button>
			<button onClick={decrement}>감소</button>
			<button onClick={reset}>리셋</button>
		</>
	);
};

export default CountButtons;

import { useCountContext } from "../hooks/useCountContext";

const CountDisplay = () => {
	const { count } = useCountContext();
	return (
		<>
			<p>Count(using contextapi): {count}</p>
		</>
	);
};

export default CountDisplay;


import { useCountContext } from "../hooks/useCountContext";

const CountDisplayExternal = () => {
	const { count } = useCountContext();
	return (
		<>
			<p>Count display external: {count}</p>
		</>
	);
};

export default CountDisplayExternal;

3. Zustand

  • 핵심 개념인 store는 현재 상태와 상태를 변경할 수 있는 함수(액션)을 함께 담고 있는 객체임.
// store 정의
import { create } from "zustand";

interface CountStoreState {
	count: number;
	increment: () => void;
	decrement: () => void;
	reset: () => void;
	resetIfEven: () => void;
}

export const useCounterStore = create<CountStoreState>((set, get) => ({
	count: 0,
	increment: () => set((state) => ({ count: state.count + 1 })),
	decrement: () => set((state) => ({ count: state.count - 1 })),
	reset: () => set({ count: 0 }),
	resetIfEven: () => {
		const { count } = get();
		if (count % 2 === 0) {
			set({ count: 0 });
		}
	},
}));


// 사용하기
import { useCounterStore } from "../store/countstore";

const CountButtons = () => {
	const increment = useCounterStore((state) => state.increment);
	const decrement = useCounterStore((state) => state.decrement);
	const reset = useCounterStore((state) => state.reset);

	return (
		<>
			<button onClick={increment}>증가</button>
			<button onClick={decrement}>감소</button>
			<button onClick={reset}>초기화</button>
		</>
	);
};

export default CountButtons;



import { useCounterStore } from "../store/countstore";

const CountDisplay = () => {
	const { count } = useCounterStore();
	return (
		<>
			<p>Count(using zustand): {count}</p>
		</>
	);
};

export default CountDisplay;
  • Zustand는 자주 사용되는 persist, immer, subscriberWithSelector, devtools 등의 미들웨어가 있음.
import { create } from 'zustand'
import { persist, subscribeWithSelector } from 'zustand/middleware'

export const useCounterStore = create<>() (
  persist( // 로컬 스토리지에 저장할 수 있도록 persist()로 감싸기
    (set, get) => ({}),
    {name: 'counter-store'} // 로컬 스토리지에서 사용할 키 이름
import { create } from 'zustand'
import { persist, subscribeWithSelector } from 'zustand/middleware'


export const useCounterStore = create<>() (
  subscribeWithSelector(
    persist( // 로컬 스토리지에 저장할 수 있도록 persist()로 감싸기
      (set, get) => ({}),
      {name: 'counter-store'} // 로컬 스토리지에서 사용할 키 이름
    )
  )
);
  • immer
    상태를 변경할 경우에는 반드시 불변성을 지켜야 한다. immer middleware를 사용하면 상태를 직접 수정하듯 간단하게 작성해도 내부 불변성을 유지하게 해 준다.
export const useCounterStore = create<>() (
  subscribeWithSelector(
    persist( // 로컬 스토리지에 저장할 수 있도록 persist()로 감싸기
      immer((set, get) => ({
        count: 0,
        increment: () => set((state) => {state.count += 1},
        decrement: () => set((state) => {state.count -= 1},
        reset: () => set((state) => {state.count = 0},        
        ...
        })
      ),
      {name: 'counter-store'} // 로컬 스토리지에서 사용할 키 이름
    )
  )
);

profile
클라우드쟁이

0개의 댓글