[데브코스/TIL] DAY52~53 - 전역 상태 관리 라이브러리

Minha Ahn·2025년 1월 3일
0

데브코스

목록 보기
35/42
post-thumbnail

📡 Context API

  • 리액트에 내장되어 있는 라이브러리
  • 리액트 컴포넌트를 같은 문맥(Context)으로 묶어, 데이터 공유를 위한 일관된 인터페이스 제공

1. 타입 생성

interface User {
  name: string;
  age: number;
}

interface AuthContextType {
  user: User | null;
  isLoggedIn: boolean;
  login: (user: User) => void;
  logout: () => void;
}

2. Context 객체 생성

// contexts/AuthContext.ts

import { createContext } from "react";

export const AuthContext = createContext<AuthContextType | null>(null);

3. 생성한 Context 객체의 Provider 생성

// contexts/providers/AuthProvider.ts

export default function AuthProvider({ children }: { children: React.ReactNode }) {
	const [user, setUser] = useState<User | null>(null);
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  const login = (user: User) => {
    setUser(user);
    setIsLoggedIn(true);
  };
  
  const logout = () => {
    setUser(null);
    setIsLoggedIn(false);
  };
  
  return (
    <AuthContext.Provider value={{ user, isLoggedIn, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

4. 전역 상태 공유

// main.tsx

import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import AuthProvider from "./contexts/providers/AuthProvider.tsx";

createRoot(document.getElementById("root")!).render(
  <AuthProvider>
    <App />
  </AuthProvider>
);

5. 필요 시 사용

  • useContext 훅을 사용하여 필요한 컨텍스트 호출
import React, { useContext } from "react";
import { AuthContext } from "../contexts/AuthContext";

export default function AuthCheck({ children }: { children: React.ReactNode }) {
  const { isLoggedIn, login, logout } = useContext(AuthContext)!;
  return (
    <>
      {isLoggedIn && children}
      {!isLoggedIn && (
        <button onClick={() => login({ name: "James", age: 20 })}>
          로그인
        </button>
      )}
      {isLoggedIn && <button onClick={logout}>로그아웃</button>}
    </>
  );
}

번외 - Context API에 메모이제이션 적용

1) value를 다루는 함수에 메모이제이션 적용

// main.tsx

import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import CounterProvider from "./context/provider/CounterProvider.tsx";

createRoot(document.getElementById("root")!).render(
  <CounterProvider>
    <App />
  </CounterProvider>
);
// context/CounterContext.ts

import { createContext } from "react";

interface CounterContextType {
  count: number;
}

interface CounterActionContextType {
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

// count 값 제공하는 컨텍스트
export const CounterContext = createContext<CounterContextType | null>(null);

// count 값을 다루는 함수를 제공하는 컨텍스트 (메모이제이션)
export const CounterActionContext =
  createContext<CounterActionContextType | null>(null);
// context/provider/CounterProvider.tsx

import { ReactNode, useMemo, useState } from "react";
import { CounterActionContext, CounterContext } from "../CounterContext";

export default function CounterProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = useState(0);

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

	// 함수지만 객체로 감싸 하나의 값으로 만들었기에 useMemo 적용
  const memo = useMemo(() => ({ increment, decrement, reset }), []);

  return (
    <>
      <CounterActionContext.Provider value={ memo }>
        <CounterContext.Provider value={{ count }}>
          {children}
        </CounterContext.Provider>
      </CounterActionContext.Provider>
    </>
  );
}

2) 잘못된 메모이제이션

// 함수에 useCallback을 적용한 건 문제가 되지 않으나
// value로 함수를 넘길 때, 객체로 묶어서 전달하는 과정에서 count의 변경으로
// 객체의 참조값이 변경되어 메모이제이션에 제대로 안됨

const increment = useCallback(() => {
  setCount((count) => count + 1);
}, []);

const decrement = useCallback(() => {
  setCount((count) => count + 1);
}, []);

const reset = useCallback(() => {
  setCount(0);
}, []);

return (
  <>
    <CounterActionContext.Provider value={{ increment, decrement, reset }}>
      <CounterContext.Provider value={{ count }}>
        {children}
      </CounterContext.Provider>
    </CounterActionContext.Provider>
  </>
);



📡 Redux-toolkit

  • 기본적인 설정과 코드량이 많아 다소 러닝 커브가 높은 전역 상태 관리 라이브러리
  • toolkit으로 redux를 쉽게 사용할 수 있음
  • 크게 2가지로 구분 ⇒ 상태, 액션(상태를 변경하기 위해 조작하는 함수)
  • 슬라이스 안에 상태와 액션을 정의해서 사용 (슬라이스는 여러 개 있을 수 있음)

1. 슬라이스 생성하기

  • createSlice : 슬라이스에 필요한 액션 생성자(actions)와 리듀서(reducer) 생성
import { createSlice } from "@reduxjs/toolkit";

interface User {
  name: string;
  agE: number;
}

const authSlice = createSlice({
  name: "authSlice",
  // 초기 상태
  initialState: {
    user: null as User | null,
    isLoggedIn: false,
  },
  // 상태를 변경하는 함수 정의 (액션)
  reducers: {
    login: (state, action) => {
      state.user = action.payload;
      state.isLoggedIn = true;
    },
    logout: (state) => {
      state.user = null;
      state.isLoggedIn = false;
    },
  },
});

export const { login, logout } = authSlice.actions;
export default authSlice.reducer;

2. 스토어 생성하기

  • configure : configurre 객체로 스토어 생성
  • reducer : 여러 개의 슬라이스를 결합하여 하나의 스토어로 관리할 수 있도록
import { configureStore } from "@reduxjs/toolkit";
import authSlice from "./slice/authSlice";
import { counterSlice } from "./slice/counterSlice";

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    auth: authSlice,
  },
});

export type RootState = ReturnType<typeof store.getState>; // 상태를 위한 타입
export type AppDispatch = typeof store.dispatch; // 액션을 위한 타입

3. 필요 시 사용

  • 변수: useSelector를 사용하여 리듀서에 접근 -> 원하는 슬라이스로 접근
  • 함수: useDispatch로 원하는 함수 사용
export default function AuthCheck({ children }: { children: React.ReactNode }) {
  const user = useSelector((state: RootState) => state.auth.user);
  const isLoggedIn = useSelector((state: RootState) => state.auth.isLoggedIn);
  const dispatch = useDispatch<AppDispatch>();

  return (
    <>
      {isLoggedIn && user?.name}
      {isLoggedIn && children}
      {!isLoggedIn && (
        <button onClick={() => dispatch(login({ name: "James", age: 20 }))}>
          로그인
        </button>
      )}
      {isLoggedIn && (
        <button onClick={() => dispatch(logout())}>로그아웃</button>
      )}
    </>
  );
}



📡 Zustand

  • 사용법 매우 간단. 로직도 간단. 가벼움
  • 리액트 훅처럼 선언하여 사용
  • 리액트에 종속적인 것이 아닌 JS를 위한 라이브러리
  • 앞으로 더 인기가 많아질 라이브러리가 될 것

1. 상태 store 생성하기

  • 상태 store : 공유되는 상태를 저장하고 관리하는 중앙 저장소를 의미함
    • 전역에서 접근 가능
    • 상태를 변경하는 로직을 스토어 안에 두어 관리
interface CounterStoreType {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

export const useCounterStore = create<CounterStoreType>((set) => ({
  count: 0, // 초기 상태
  increment: () => set((state) => ({ count: state.count + 1 })), // 상태 변경 로직
  decrement: () => set((state) => ({ count: state.count - 1 })), // 상태 변경 로직
  reset: () => set({ count: 0 }), // 상태 변경 로직
}));

2. 상태 store 사용하기

export default function App() {
	const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
	const decrement = useCounterStore((state) => state.decrement);
  
  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={decrement}>감소</button>
      <button onClick={increment}>증가</button>
    </>
  );
;

3. 잘못된 상태 구독

const { count, increment, decrement } = useCounterStore();
  • 위의 방식은 상태 store를 전체 구독하는 방식
  • 상태가 변경될 때마다 스토어 객체 전체가 새로운 참조값을 가지므로 컴포넌트의 리렌더링 발생
    • 이로 인해 컴포넌트에 React.memo 처리를 한 경우, 무의미해짐





📌 출처

수코딩(https://www.sucoding.kr)

profile
프론트엔드를 공부하고 있는 학생입니다🐌

0개의 댓글

관련 채용 정보